Merge branch 'charlesmchen/nse2021.10.21.2'
This commit is contained in:
parent
749b4b9cae
commit
631633b76a
2
Pods
2
Pods
@ -1 +1 @@
|
||||
Subproject commit 5eb2e77c4768eed576f576a9eaf0b07fbfa865a2
|
||||
Subproject commit f6c13f81b9571440b2cf512c44089e36333854ed
|
||||
@ -240,6 +240,7 @@
|
||||
342FFE7A271EF581000AC89F /* UIResponder+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342FFE73271EF580000AC89F /* UIResponder+OWS.swift */; };
|
||||
342FFE7B271EF581000AC89F /* UIStoryboard+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342FFE74271EF580000AC89F /* UIStoryboard+OWS.swift */; };
|
||||
342FFE7E271EF5B2000AC89F /* ReturnToCallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342FFE7D271EF5B1000AC89F /* ReturnToCallViewController.swift */; };
|
||||
342FFE832721D4B6000AC89F /* ContactsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342FFE822721D4B6000AC89F /* ContactsState.swift */; };
|
||||
34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
|
||||
343417F12530A7480034FE0C /* CVComponentReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 343417F02530A7480034FE0C /* CVComponentReactions.swift */; };
|
||||
3434AE1C22AEDE7D002EE04E /* ViewOnceMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3434AE1B22AEDE7D002EE04E /* ViewOnceMessageViewController.swift */; };
|
||||
@ -1235,6 +1236,7 @@
|
||||
342FFE73271EF580000AC89F /* UIResponder+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIResponder+OWS.swift"; sourceTree = "<group>"; };
|
||||
342FFE74271EF580000AC89F /* UIStoryboard+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+OWS.swift"; sourceTree = "<group>"; };
|
||||
342FFE7D271EF5B1000AC89F /* ReturnToCallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReturnToCallViewController.swift; sourceTree = "<group>"; };
|
||||
342FFE822721D4B6000AC89F /* ContactsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsState.swift; sourceTree = "<group>"; };
|
||||
34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = "<group>"; };
|
||||
343417F02530A7480034FE0C /* CVComponentReactions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CVComponentReactions.swift; sourceTree = "<group>"; };
|
||||
3434AE1B22AEDE7D002EE04E /* ViewOnceMessageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewOnceMessageViewController.swift; sourceTree = "<group>"; };
|
||||
@ -2747,6 +2749,7 @@
|
||||
346129A11FD1F09100532771 /* contacts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
342FFE822721D4B6000AC89F /* ContactsState.swift */,
|
||||
4C9D347E23689E06006A4307 /* IncomingContactSyncJobQueue.swift */,
|
||||
4C6E446822AEDDEE007982E6 /* NewAccountDiscovery.swift */,
|
||||
346129A21FD1F09100532771 /* OWSContactsManager.h */,
|
||||
@ -5721,6 +5724,7 @@
|
||||
files = (
|
||||
88D23D0C23CEBF4400B0E74B /* AppNotifications.swift in Sources */,
|
||||
4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */,
|
||||
342FFE832721D4B6000AC89F /* ContactsState.swift in Sources */,
|
||||
88D7BA9E266809F50088D1C2 /* CallMessageRelay.swift in Sources */,
|
||||
4C046AA7236148880035B234 /* OWSGroupSyncProcessingJobQueue.swift in Sources */,
|
||||
4C9D347F23689E06006A4307 /* IncomingContactSyncJobQueue.swift in Sources */,
|
||||
|
||||
@ -198,7 +198,8 @@ import SignalMessaging
|
||||
// * The thread belongs to someone in our system contacts
|
||||
// * The thread existed before messages requests
|
||||
return (self.profileManager.isThread(inProfileWhitelist: thread, transaction: transaction)
|
||||
|| self.contactsManager.isSystemContact(address: thread.contactAddress)
|
||||
|| self.contactsManager.isSystemContact(address: thread.contactAddress,
|
||||
transaction: transaction)
|
||||
|| GRDBThreadFinder.isPreMessageRequestsThread(thread, transaction: transaction.unwrapGrdbRead))
|
||||
}
|
||||
|
||||
|
||||
@ -138,7 +138,9 @@ class ContactViewController: OWSViewController, ContactShareViewHelperDelegate {
|
||||
private func systemContactsForContact() -> [String] {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
return contactShare.systemContactPhoneNumbers()
|
||||
return databaseStorage.read { transaction in
|
||||
contactShare.systemContactPhoneNumbers(transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateContent() {
|
||||
|
||||
@ -883,7 +883,7 @@ fileprivate extension CVComponentState.Builder {
|
||||
self.contactShare = ContactShare(state: state)
|
||||
|
||||
let hasSendTextButton = !contactShare.systemContactsWithSignalAccountPhoneNumbers(transaction: transaction).isEmpty
|
||||
let hasInviteButton = !contactShare.systemContactPhoneNumbers().isEmpty
|
||||
let hasInviteButton = !contactShare.systemContactPhoneNumbers(transaction: transaction).isEmpty
|
||||
let hasAddToContactsButton = !contactShare.e164PhoneNumbers().isEmpty
|
||||
|
||||
if hasSendTextButton {
|
||||
|
||||
@ -1084,7 +1084,8 @@ extension CVComponentSystemMessage {
|
||||
guard let profileChangeNewNameComponents = infoMessage.profileChangeNewNameComponents else {
|
||||
return nil
|
||||
}
|
||||
guard Self.contactsManager.isSystemContact(address: profileChangeAddress) else {
|
||||
guard Self.contactsManager.isSystemContact(address: profileChangeAddress,
|
||||
transaction: transaction) else {
|
||||
return nil
|
||||
}
|
||||
let systemContactName = Self.contactsManagerImpl.nameFromSystemContacts(for: profileChangeAddress,
|
||||
|
||||
@ -21,7 +21,10 @@ extension ConversationViewController {
|
||||
}
|
||||
|
||||
// If the user is in the system contacts, show a badge
|
||||
if self.contactsManagerImpl.isSystemContact(address: contactThread.contactAddress) {
|
||||
let isSystemContact = databaseStorage.read { transaction in
|
||||
contactsManagerImpl.isSystemContact(address: contactThread.contactAddress, transaction: transaction)
|
||||
}
|
||||
if isSystemContact {
|
||||
icon = UIImage(named: "contact-outline-16")?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
} else if let groupThread = thread as? TSGroupThread {
|
||||
|
||||
@ -153,10 +153,12 @@ class DebugUIGroupsV2: DebugUIPage {
|
||||
|
||||
let prefix = prefix + " "
|
||||
|
||||
let signalAccounts = self.contactsManagerImpl.unsortedSignalAccounts(transaction: transaction)
|
||||
|
||||
// These will fail if you aren't registered or
|
||||
// don't have some Signal users in your contacts.
|
||||
let localAddress = tsAccountManager.localAddress!
|
||||
var allAddresses = contactsManagerImpl.signalAccounts.map { $0.recipientAddress }
|
||||
var allAddresses = signalAccounts.map { $0.recipientAddress }
|
||||
if groupsVersion == .V2 {
|
||||
// V2 group members must have a uuid.
|
||||
allAddresses = allAddresses.filter { $0.uuid != nil }
|
||||
|
||||
@ -277,9 +277,12 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
|
||||
|
||||
- (NSArray<SignalAccount *> *)suggestedAccountsForFirstContact
|
||||
{
|
||||
// Load all signal accounts even though we only need the first N;
|
||||
// we want the returned value to be stable so we need to sort.
|
||||
NSArray<SignalAccount *> *sortedSignalAccounts = [self.contactsManagerImpl sortedSignalAccountsWithSneakyTransaction];
|
||||
|
||||
NSMutableArray<SignalAccount *> *accounts = [NSMutableArray new];
|
||||
|
||||
for (SignalAccount *account in self.contactsManager.signalAccounts) {
|
||||
for (SignalAccount *account in sortedSignalAccounts) {
|
||||
if (account.recipientAddress.isLocalAddress) {
|
||||
continue;
|
||||
}
|
||||
@ -294,33 +297,41 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
|
||||
|
||||
- (void)updateFirstConversationLabel
|
||||
{
|
||||
|
||||
NSArray<SignalAccount *> *signalAccounts = self.suggestedAccountsForFirstContact;
|
||||
|
||||
NSString *formatString = @"";
|
||||
__block NSString *formatString = @"";
|
||||
NSMutableArray<NSString *> *contactNames = [NSMutableArray new];
|
||||
if (signalAccounts.count >= 3) {
|
||||
[contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[0]]];
|
||||
[contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[1]]];
|
||||
[contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[2]]];
|
||||
|
||||
formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_3_CONTACTS_FORMAT",
|
||||
@"Format string for a label offering to start a new conversation with your contacts, if you have at least "
|
||||
@"3 Signal contacts. Embeds {{The names of 3 of your Signal contacts}}.");
|
||||
} else if (signalAccounts.count == 2) {
|
||||
[contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[0]]];
|
||||
[contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[1]]];
|
||||
|
||||
formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_2_CONTACTS_FORMAT",
|
||||
@"Format string for a label offering to start a new conversation with your contacts, if you have 2 Signal "
|
||||
@"contacts. Embeds {{The names of 2 of your Signal contacts}}.");
|
||||
} else if (signalAccounts.count == 1) {
|
||||
[contactNames addObject:[self.contactsManager displayNameForSignalAccount:signalAccounts[0]]];
|
||||
|
||||
formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_1_CONTACT_FORMAT",
|
||||
@"Format string for a label offering to start a new conversation with your contacts, if you have 1 Signal "
|
||||
@"contact. Embeds {{The name of 1 of your Signal contacts}}.");
|
||||
}
|
||||
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
if (signalAccounts.count >= 3) {
|
||||
[contactNames addObject:[self.contactsManagerImpl displayNameForSignalAccount:signalAccounts[0]
|
||||
transaction:transaction]];
|
||||
[contactNames addObject:[self.contactsManagerImpl displayNameForSignalAccount:signalAccounts[1]
|
||||
transaction:transaction]];
|
||||
[contactNames addObject:[self.contactsManagerImpl displayNameForSignalAccount:signalAccounts[2]
|
||||
transaction:transaction]];
|
||||
|
||||
formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_3_CONTACTS_FORMAT",
|
||||
@"Format string for a label offering to start a new conversation with your contacts, if you have at least "
|
||||
@"3 Signal contacts. Embeds {{The names of 3 of your Signal contacts}}.");
|
||||
} else if (signalAccounts.count == 2) {
|
||||
[contactNames addObject:[self.contactsManagerImpl displayNameForSignalAccount:signalAccounts[0]
|
||||
transaction:transaction]];
|
||||
[contactNames addObject:[self.contactsManagerImpl displayNameForSignalAccount:signalAccounts[1]
|
||||
transaction:transaction]];
|
||||
|
||||
formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_2_CONTACTS_FORMAT",
|
||||
@"Format string for a label offering to start a new conversation with your contacts, if you have 2 Signal "
|
||||
@"contacts. Embeds {{The names of 2 of your Signal contacts}}.");
|
||||
} else if (signalAccounts.count == 1) {
|
||||
[contactNames addObject:[self.contactsManagerImpl displayNameForSignalAccount:signalAccounts[0]
|
||||
transaction:transaction]];
|
||||
|
||||
formatString = NSLocalizedString(@"HOME_VIEW_FIRST_CONVERSATION_OFFER_1_CONTACT_FORMAT",
|
||||
@"Format string for a label offering to start a new conversation with your contacts, if you have 1 Signal "
|
||||
@"contact. Embeds {{The name of 1 of your Signal contacts}}.");
|
||||
}
|
||||
}];
|
||||
|
||||
NSString *embedToken = @"%@";
|
||||
NSArray<NSString *> *formatSplits = [formatString componentsSeparatedByString:embedToken];
|
||||
|
||||
@ -31,8 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (NSString *)stringForCollation
|
||||
{
|
||||
OWSContactsManager *contactsManager = Environment.shared.contactsManager;
|
||||
return [contactsManager comparableNameForSignalAccount:self];
|
||||
return [self.contactsManagerImpl comparableNameForSignalAccountWithSneakyTransaction:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -26,7 +26,9 @@ extension ConversationSettingsViewController {
|
||||
owsFailDebug("Invalid thread.")
|
||||
return false
|
||||
}
|
||||
return contactsManagerImpl.isSystemContact(address: contactThread.contactAddress)
|
||||
return databaseStorage.read { transaction in
|
||||
contactsManagerImpl.isSystemContact(address: contactThread.contactAddress, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table
|
||||
|
||||
@ -257,7 +257,10 @@ class MemberActionSheet: InteractiveSheetViewController {
|
||||
))
|
||||
|
||||
if contactsManagerImpl.supportsContactEditing {
|
||||
if contactsManager.isSystemContact(address: address) {
|
||||
let isSystemContact = databaseStorage.read { transaction in
|
||||
contactsManager.isSystemContact(address: address, transaction: transaction)
|
||||
}
|
||||
if isSystemContact {
|
||||
section.add(.actionItem(
|
||||
icon: .settingsUserInContacts,
|
||||
name: NSLocalizedString(
|
||||
|
||||
@ -300,7 +300,9 @@ class NameCollisionResolutionViewController: OWSTableViewController2 {
|
||||
extension NameCollisionResolutionViewController: CNContactViewControllerDelegate, ContactsViewHelperObserver {
|
||||
|
||||
func shouldShowContactUpdateAction(for address: SignalServiceAddress) -> Bool {
|
||||
return contactsManager.isSystemContact(address: address) && contactsManagerImpl.supportsContactEditing
|
||||
databaseStorage.read { transaction in
|
||||
contactsManager.isSystemContact(address: address, transaction: transaction) && contactsManagerImpl.supportsContactEditing
|
||||
}
|
||||
}
|
||||
|
||||
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
|
||||
|
||||
@ -84,7 +84,9 @@ public class OWSAddToContactViewController: OWSViewController {
|
||||
}
|
||||
|
||||
private func updateData() {
|
||||
contacts = contactsManagerImpl.allContacts
|
||||
contacts = databaseStorage.read { transaction in
|
||||
contactsManagerImpl.allSortedContacts(transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ extension NameCollision {
|
||||
commonGroupsString: commonGroupsString(for: $0.address, thread: thread, transaction: transaction),
|
||||
avatar: avatar(for: $0.address, transaction: transaction),
|
||||
isBlocked: blockingManager.isAddressBlocked($0.address),
|
||||
isSystemContact: Self.contactsManager.isSystemContact(address: $0.address)
|
||||
isSystemContact: Self.contactsManager.isSystemContact(address: $0.address, transaction: transaction)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,10 +62,6 @@ class GRDBFullTextSearcherContactsManager: NSObject, ContactsManagerProtocol {
|
||||
"Fake name"
|
||||
}
|
||||
|
||||
func nameComponents(for address: SignalServiceAddress) -> PersonNameComponents? {
|
||||
PersonNameComponents()
|
||||
}
|
||||
|
||||
func nameComponents(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> PersonNameComponents? {
|
||||
PersonNameComponents()
|
||||
}
|
||||
@ -74,12 +70,20 @@ class GRDBFullTextSearcherContactsManager: NSObject, ContactsManagerProtocol {
|
||||
[]
|
||||
}
|
||||
|
||||
func isSystemContact(phoneNumber: String) -> Bool {
|
||||
true
|
||||
func isSystemContactWithSneakyTransaction(phoneNumber: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContact(address: SignalServiceAddress) -> Bool {
|
||||
true
|
||||
func isSystemContact(phoneNumber: String, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContactWithSneakyTransaction(address: SignalServiceAddress) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContact(address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContact(withSignalAccount recipientId: String) -> Bool {
|
||||
@ -90,10 +94,6 @@ class GRDBFullTextSearcherContactsManager: NSObject, ContactsManagerProtocol {
|
||||
true
|
||||
}
|
||||
|
||||
func hasNameInSystemContacts(for address: SignalServiceAddress) -> Bool {
|
||||
(mockDisplayNameMap[address] ?? "").count > 0
|
||||
}
|
||||
|
||||
func compare(signalAccount left: SignalAccount, with right: SignalAccount) -> ComparisonResult {
|
||||
owsFailDebug("if this method ends up being used by the tests, we should provide a better implementation.")
|
||||
|
||||
|
||||
@ -361,10 +361,6 @@ class TestContactsManager: NSObject, ContactsManagerProtocol {
|
||||
address.stringForDisplay
|
||||
}
|
||||
|
||||
func nameComponents(for address: SignalServiceAddress) -> PersonNameComponents? {
|
||||
PersonNameComponents()
|
||||
}
|
||||
|
||||
func nameComponents(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> PersonNameComponents? {
|
||||
PersonNameComponents()
|
||||
}
|
||||
@ -373,12 +369,20 @@ class TestContactsManager: NSObject, ContactsManagerProtocol {
|
||||
[]
|
||||
}
|
||||
|
||||
func isSystemContact(phoneNumber: String) -> Bool {
|
||||
true
|
||||
func isSystemContactWithSneakyTransaction(phoneNumber: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContact(address: SignalServiceAddress) -> Bool {
|
||||
true
|
||||
func isSystemContact(phoneNumber: String, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContactWithSneakyTransaction(address: SignalServiceAddress) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContact(address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isSystemContact(withSignalAccount phoneNumber: String) -> Bool {
|
||||
@ -389,10 +393,6 @@ class TestContactsManager: NSObject, ContactsManagerProtocol {
|
||||
true
|
||||
}
|
||||
|
||||
func hasNameInSystemContacts(for address: SignalServiceAddress) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
func compare(signalAccount left: SignalAccount, with right: SignalAccount) -> ComparisonResult {
|
||||
.orderedSame
|
||||
}
|
||||
|
||||
382
SignalMessaging/contacts/ContactsState.swift
Normal file
382
SignalMessaging/contacts/ContactsState.swift
Normal file
@ -0,0 +1,382 @@
|
||||
//
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
|
||||
@objc
|
||||
public protocol ContactsState: AnyObject {
|
||||
func unsortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount]
|
||||
|
||||
// Order respects the systems contact sorting preference.
|
||||
func sortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount]
|
||||
|
||||
func setSortedSignalAccounts(_ signalAccounts: [SignalAccount])
|
||||
|
||||
func contact(forPhoneNumber phoneNumber: String, transaction: SDSAnyReadTransaction) -> Contact?
|
||||
|
||||
func allContacts(transaction: SDSAnyReadTransaction) -> [Contact]
|
||||
|
||||
func contactsMaps(transaction: SDSAnyReadTransaction) -> ContactsMaps
|
||||
|
||||
func setContactsMaps(_ contactsMaps: ContactsMaps,
|
||||
localNumber: String?,
|
||||
transaction: SDSAnyWriteTransaction)
|
||||
|
||||
func warmCaches(transaction: SDSAnyReadTransaction) -> ContactsStateSummary
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class ContactsStateSummary: NSObject {
|
||||
@objc
|
||||
public let contactCount: UInt
|
||||
@objc
|
||||
public let signalAccountCount: UInt
|
||||
|
||||
required init(contactCount: UInt, signalAccountCount: UInt) {
|
||||
self.contactCount = contactCount
|
||||
self.signalAccountCount = signalAccountCount
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class ContactsMaps: NSObject {
|
||||
@objc
|
||||
public let uniqueIdToContactMap: [String: Contact]
|
||||
@objc
|
||||
public let phoneNumberToContactMap: [String: Contact]
|
||||
|
||||
@objc
|
||||
public var allContacts: [Contact] { Array(uniqueIdToContactMap.values) }
|
||||
|
||||
required init(uniqueIdToContactMap: [String: Contact],
|
||||
phoneNumberToContactMap: [String: Contact]) {
|
||||
self.uniqueIdToContactMap = uniqueIdToContactMap
|
||||
self.phoneNumberToContactMap = phoneNumberToContactMap
|
||||
}
|
||||
|
||||
static let empty: ContactsMaps = ContactsMaps(uniqueIdToContactMap: [:], phoneNumberToContactMap: [:])
|
||||
|
||||
// Builds a map of phone number-to-Contact.
|
||||
// A given Contact may have multiple phone numbers.
|
||||
@objc
|
||||
public static func build(contacts: [Contact],
|
||||
localNumber: String?) -> ContactsMaps {
|
||||
|
||||
var uniqueIdToContactMap = [String: Contact]()
|
||||
var phoneNumberToContactMap = [String: Contact]()
|
||||
for contact in contacts {
|
||||
let phoneNumbers = Self.phoneNumbers(forContact: contact, localNumber: localNumber)
|
||||
guard !phoneNumbers.isEmpty else {
|
||||
continue
|
||||
}
|
||||
|
||||
uniqueIdToContactMap[contact.uniqueId] = contact
|
||||
|
||||
for phoneNumber in phoneNumbers {
|
||||
phoneNumberToContactMap[phoneNumber] = contact
|
||||
}
|
||||
}
|
||||
return ContactsMaps(uniqueIdToContactMap: uniqueIdToContactMap,
|
||||
phoneNumberToContactMap: phoneNumberToContactMap)
|
||||
}
|
||||
|
||||
fileprivate static func phoneNumbers(forContact contact: Contact, localNumber: String?) -> [String] {
|
||||
return contact.parsedPhoneNumbers.compactMap { phoneNumber in
|
||||
guard let phoneNumberE164 = phoneNumber.toE164().nilIfEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore any system contact records for the local contact.
|
||||
// For the local user we never want to show the avatar /
|
||||
// name that you have entered for yourself in your system
|
||||
// contacts. Instead, we always want to display your profile
|
||||
// name and avatar.
|
||||
let isLocalContact = phoneNumberE164 == localNumber
|
||||
guard !isLocalContact else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return phoneNumberE164
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func isSystemContact(address: SignalServiceAddress) -> Bool {
|
||||
guard let phoneNumber = address.phoneNumber?.nilIfEmpty else {
|
||||
return false
|
||||
}
|
||||
return isSystemContact(phoneNumber: phoneNumber)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func isSystemContact(phoneNumber: String) -> Bool {
|
||||
phoneNumberToContactMap[phoneNumber] != nil
|
||||
}
|
||||
|
||||
public func isEqualForCache(_ other: ContactsMaps) -> Bool {
|
||||
let mapSelf = self.uniqueIdToContactMap
|
||||
let mapOther = other.uniqueIdToContactMap
|
||||
let keysSelf = Set(mapSelf.keys)
|
||||
let keysOther = Set(mapOther.keys)
|
||||
guard keysSelf == keysOther else {
|
||||
return false
|
||||
}
|
||||
for key in keysSelf {
|
||||
guard let valueSelf = mapSelf[key],
|
||||
let valueOther = mapOther[key],
|
||||
valueSelf.isEqualForCache(valueOther) else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class ContactsStateInDatabase: NSObject, ContactsState {
|
||||
@objc
|
||||
public func unsortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount] {
|
||||
SignalAccount.anyFetchAll(transaction: transaction)
|
||||
}
|
||||
|
||||
// Order respects the systems contact sorting preference.
|
||||
@objc
|
||||
public func sortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount] {
|
||||
contactsManagerImpl.sortSignalAccounts(unsortedSignalAccounts(transaction: transaction),
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func setSortedSignalAccounts(_ signalAccounts: [SignalAccount]) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
private static let uniqueIdStore = SDSKeyValueStore(collection: "ContactsState.uniqueIdStore")
|
||||
private static let phoneNumberStore = SDSKeyValueStore(collection: "ContactsState.phoneNumberStore")
|
||||
|
||||
private static func serializeContact(_ contact: Contact, label: String) -> Data? {
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: contact)
|
||||
guard !data.isEmpty else {
|
||||
owsFailDebug("Could not serialize contact: \(label).")
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private static func deserializeContact(data: Data, label: String) -> Contact? {
|
||||
do {
|
||||
guard let contact = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Contact else {
|
||||
owsFailDebug("Invalid value: \(label).")
|
||||
return nil
|
||||
}
|
||||
return contact
|
||||
} catch {
|
||||
owsFailDebug("Deserialize failed[\(label)]: \(error).")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func contact(forPhoneNumber phoneNumber: String, transaction: SDSAnyReadTransaction) -> Contact? {
|
||||
guard let data = Self.phoneNumberStore.getData(phoneNumber, transaction: transaction) else {
|
||||
return nil
|
||||
}
|
||||
return Self.deserializeContact(data: data, label: phoneNumber)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func allContacts(transaction: SDSAnyReadTransaction) -> [Contact] {
|
||||
var contacts = [Contact]()
|
||||
Self.uniqueIdStore.enumerateKeys(transaction: transaction) { (key, _) in
|
||||
guard let data = Self.uniqueIdStore.getData(key, transaction: transaction) else {
|
||||
owsFailDebug("Missing data for key: \(key).")
|
||||
return
|
||||
}
|
||||
guard let contact = Self.deserializeContact(data: data, label: key) else {
|
||||
return
|
||||
}
|
||||
contacts.append(contact)
|
||||
}
|
||||
return contacts
|
||||
}
|
||||
|
||||
@objc
|
||||
public func contactsMaps(transaction: SDSAnyReadTransaction) -> ContactsMaps {
|
||||
let contacts = allContacts(transaction: transaction)
|
||||
let localNumber: String? = tsAccountManager.localNumber(with: transaction)
|
||||
return ContactsMaps.build(contacts: contacts, localNumber: localNumber)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func setContactsMaps(_ newContactsMaps: ContactsMaps,
|
||||
localNumber: String?,
|
||||
transaction: SDSAnyWriteTransaction) {
|
||||
|
||||
let oldContactsMaps = self.contactsMaps(transaction: transaction)
|
||||
if oldContactsMaps.isEqualForCache(newContactsMaps) {
|
||||
Logger.verbose("Ignoring redundant contactsMap update.")
|
||||
return
|
||||
}
|
||||
|
||||
Self.uniqueIdStore.removeAll(transaction: transaction)
|
||||
Self.phoneNumberStore.removeAll(transaction: transaction)
|
||||
|
||||
for contact in newContactsMaps.uniqueIdToContactMap.values {
|
||||
let phoneNumbers = ContactsMaps.phoneNumbers(forContact: contact, localNumber: localNumber)
|
||||
guard !phoneNumbers.isEmpty else {
|
||||
continue
|
||||
}
|
||||
guard let contactData = Self.serializeContact(contact, label: contact.uniqueId) else {
|
||||
continue
|
||||
}
|
||||
|
||||
Self.uniqueIdStore.setData(contactData, key: contact.uniqueId, transaction: transaction)
|
||||
|
||||
for phoneNumber in phoneNumbers {
|
||||
Self.phoneNumberStore.setData(contactData, key: phoneNumber, transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func warmCaches(transaction: SDSAnyReadTransaction) -> ContactsStateSummary {
|
||||
// Only load the summary.
|
||||
let contactCount = Self.uniqueIdStore.numberOfKeys(transaction: transaction)
|
||||
let signalAccountCount = SignalAccount.anyCount(transaction: transaction)
|
||||
return ContactsStateSummary(contactCount: contactCount, signalAccountCount: signalAccountCount)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class ContactsStateInMemory: NSObject, ContactsState {
|
||||
private let unfairLock = UnfairLock()
|
||||
|
||||
private let contactsStateInDatabase = ContactsStateInDatabase()
|
||||
|
||||
private var sortedSignalAccountsCache: [SignalAccount]?
|
||||
|
||||
private var contactsMapsCache: ContactsMaps?
|
||||
|
||||
@objc
|
||||
public func unsortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount] {
|
||||
// Prefer cache.
|
||||
if let cachedValue = (unfairLock.withLock { sortedSignalAccountsCache }) {
|
||||
return cachedValue
|
||||
}
|
||||
// Fail over.
|
||||
// NOTE: We use the "sorted" getter because our cache should be sorted.
|
||||
let result = contactsStateInDatabase.sortedSignalAccounts(transaction: transaction)
|
||||
unfairLock.withLock { sortedSignalAccountsCache = result }
|
||||
return result
|
||||
}
|
||||
|
||||
// Order respects the systems contact sorting preference.
|
||||
@objc
|
||||
public func sortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount] {
|
||||
// Prefer cache.
|
||||
if let cachedValue = (unfairLock.withLock { sortedSignalAccountsCache }) {
|
||||
return cachedValue
|
||||
}
|
||||
// Fail over.
|
||||
let result = contactsStateInDatabase.sortedSignalAccounts(transaction: transaction)
|
||||
unfairLock.withLock { sortedSignalAccountsCache = result }
|
||||
return result
|
||||
}
|
||||
|
||||
@objc
|
||||
public func setSortedSignalAccounts(_ signalAccounts: [SignalAccount]) {
|
||||
unfairLock.withLock {
|
||||
// Update cache.
|
||||
sortedSignalAccountsCache = signalAccounts
|
||||
}
|
||||
return contactsStateInDatabase.setSortedSignalAccounts(signalAccounts)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func contact(forPhoneNumber phoneNumber: String, transaction: SDSAnyReadTransaction) -> Contact? {
|
||||
unfairLock.withLock {
|
||||
guard let contactsMaps = contactsMapsCache else {
|
||||
owsFailDebug("Missing contactsMaps.")
|
||||
// Don't bother failing over to contactsStateInDatabase.
|
||||
return nil
|
||||
}
|
||||
return contactsMaps.phoneNumberToContactMap[phoneNumber]
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func allContacts(transaction: SDSAnyReadTransaction) -> [Contact] {
|
||||
unfairLock.withLock {
|
||||
guard let contactsMaps = contactsMapsCache else {
|
||||
owsFailDebug("Missing contactsMaps.")
|
||||
// Don't bother failing over to contactsStateInDatabase.
|
||||
return []
|
||||
}
|
||||
return Array(contactsMaps.phoneNumberToContactMap.values)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func contactsMaps(transaction: SDSAnyReadTransaction) -> ContactsMaps {
|
||||
unfairLock.withLock {
|
||||
guard let contactsMaps = contactsMapsCache else {
|
||||
owsFailDebug("Missing contactsMaps.")
|
||||
// Don't bother failing over to contactsStateInDatabase.
|
||||
return .empty
|
||||
}
|
||||
return contactsMaps
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func setContactsMaps(_ contactsMaps: ContactsMaps,
|
||||
localNumber: String?,
|
||||
transaction: SDSAnyWriteTransaction) {
|
||||
unfairLock.withLock {
|
||||
// Update cache.
|
||||
contactsMapsCache = contactsMaps
|
||||
}
|
||||
return contactsStateInDatabase.setContactsMaps(contactsMaps,
|
||||
localNumber: localNumber,
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func warmCaches(transaction: SDSAnyReadTransaction) -> ContactsStateSummary {
|
||||
|
||||
let sortedSignalAccounts = contactsStateInDatabase.sortedSignalAccounts(transaction: transaction)
|
||||
let contactsMaps = contactsStateInDatabase.contactsMaps(transaction: transaction)
|
||||
|
||||
unfairLock.withLock {
|
||||
sortedSignalAccountsCache = sortedSignalAccounts
|
||||
contactsMapsCache = contactsMaps
|
||||
}
|
||||
|
||||
// Don't call contactsStateInDatabase.warmCaches().
|
||||
return ContactsStateSummary(contactCount: UInt(contactsMaps.uniqueIdToContactMap.count),
|
||||
signalAccountCount: UInt(sortedSignalAccounts.count))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
extension OWSContactsManager {
|
||||
public func contact(forPhoneNumber phoneNumber: String, transaction: SDSAnyReadTransaction) -> Contact? {
|
||||
contactsState.contact(forPhoneNumber: phoneNumber, transaction: transaction)
|
||||
}
|
||||
|
||||
public func contactsMaps(transaction: SDSAnyReadTransaction) -> ContactsMaps {
|
||||
contactsState.contactsMaps(transaction: transaction)
|
||||
}
|
||||
}
|
||||
@ -17,29 +17,27 @@ extern NSNotificationName const OWSContactsManagerContactsDidChangeNotification;
|
||||
@class SignalServiceAddress;
|
||||
@class UIFont;
|
||||
|
||||
@protocol ContactsState;
|
||||
|
||||
/**
|
||||
* Get latest Signal contacts, and be notified when they change.
|
||||
*/
|
||||
@interface OWSContactsManager : NSObject <ContactsManagerProtocol>
|
||||
|
||||
@property (nonatomic, readonly) BOOL shouldSortByGivenName;
|
||||
|
||||
@property (nonatomic, readonly) id<ContactsState> contactsState;
|
||||
|
||||
#pragma mark - Accessors
|
||||
|
||||
@property (nonatomic, readonly) SDSKeyValueStore *keyValueStore;
|
||||
|
||||
@property (atomic, readonly) NSArray<Contact *> *allContacts;
|
||||
|
||||
@property (atomic, readonly) NSDictionary<NSString *, Contact *> *allContactsMap;
|
||||
|
||||
// order of the signalAccounts array respects the systems contact sorting preference
|
||||
@property (atomic, readonly) NSArray<SignalAccount *> *signalAccounts;
|
||||
|
||||
// This will return an instance of SignalAccount for _known_ signal accounts.
|
||||
- (nullable SignalAccount *)fetchSignalAccountForAddress:(SignalServiceAddress *)address;
|
||||
|
||||
- (nullable SignalAccount *)fetchSignalAccountForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
- (nullable NSString *)nameFromSystemContactsForAddress:(SignalServiceAddress *)address;
|
||||
- (nullable NSString *)nameFromSystemContactsForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
@ -59,7 +57,7 @@ extern NSNotificationName const OWSContactsManagerContactsDidChangeNotification;
|
||||
|
||||
// Not set until a contact fetch has completed.
|
||||
// Set even if no contacts are found.
|
||||
@property (nonatomic, readonly) BOOL hasLoadedContacts;
|
||||
@property (nonatomic, readonly) BOOL hasLoadedSystemContacts;
|
||||
|
||||
// Request systems contacts and start syncing changes. The user will see an alert
|
||||
// if they haven't previously.
|
||||
@ -77,14 +75,9 @@ extern NSNotificationName const OWSContactsManagerContactsDidChangeNotification;
|
||||
|
||||
#pragma mark - Util
|
||||
|
||||
- (BOOL)isSystemContactWithPhoneNumber:(NSString *)phoneNumber;
|
||||
- (BOOL)isSystemContactWithAddress:(SignalServiceAddress *)address;
|
||||
- (BOOL)hasNameInSystemContactsForAddress:(SignalServiceAddress *)address;
|
||||
|
||||
/**
|
||||
* Used for sorting, respects system contacts name sort order preference.
|
||||
*/
|
||||
- (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount;
|
||||
- (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
- (NSString *)comparableNameForAddress:(SignalServiceAddress *)address transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
@ -31,11 +31,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
@interface OWSContactsManager () <SystemContactsFetcherDelegate>
|
||||
|
||||
@property (nonatomic) BOOL isContactsUpdateInFlight;
|
||||
// This reflects the contents of the device phone book and includes
|
||||
// contacts that do not correspond to any signal account.
|
||||
@property (atomic) NSArray<Contact *> *allContacts;
|
||||
@property (atomic) NSDictionary<NSString *, Contact *> *allContactsMap;
|
||||
@property (atomic) NSArray<SignalAccount *> *signalAccounts;
|
||||
|
||||
@property (nonatomic, readonly) SystemContactsFetcher *systemContactsFetcher;
|
||||
@property (nonatomic, readonly) AnyLRUCache *cnContactCache;
|
||||
@ -56,14 +51,16 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
|
||||
_keyValueStore = [[SDSKeyValueStore alloc] initWithCollection:OWSContactsManagerCollection];
|
||||
|
||||
_allContacts = @[];
|
||||
_allContactsMap = @{};
|
||||
_signalAccounts = @[];
|
||||
_systemContactsFetcher = [SystemContactsFetcher new];
|
||||
_systemContactsFetcher.delegate = self;
|
||||
_cnContactCache = [[AnyLRUCache alloc] initWithMaxSize:50
|
||||
nseMaxSize:0
|
||||
shouldEvacuateInBackground:YES];
|
||||
if (CurrentAppContext().isMainApp) {
|
||||
_contactsState = [ContactsStateInMemory new];
|
||||
} else {
|
||||
_contactsState = [ContactsStateInDatabase new];
|
||||
}
|
||||
|
||||
OWSSingletonAssert();
|
||||
|
||||
@ -75,19 +72,14 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
}
|
||||
|
||||
- (void)setup {
|
||||
__block NSMutableArray<SignalAccount *> *signalAccounts;
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
NSUInteger signalAccountCount = [SignalAccount anyCountWithTransaction:transaction];
|
||||
OWSLogInfo(@"loading %lu signal accounts from cache.", (unsigned long)signalAccountCount);
|
||||
|
||||
signalAccounts = [[NSMutableArray alloc] initWithCapacity:signalAccountCount];
|
||||
|
||||
[SignalAccount anyEnumerateWithTransaction:transaction
|
||||
block:^(SignalAccount *signalAccount, BOOL *stop) {
|
||||
[signalAccounts addObject:signalAccount];
|
||||
}];
|
||||
ContactsStateSummary *contactsStateSummary = [self.contactsState warmCachesWithTransaction:transaction];
|
||||
OWSLogInfo(@"Loaded %lu contacts, %lu signal accounts from cache.",
|
||||
(unsigned long)contactsStateSummary.contactCount,
|
||||
(unsigned long)contactsStateSummary.signalAccountCount);
|
||||
}];
|
||||
[self updateSignalAccounts:signalAccounts shouldSetHasLoadedContacts:NO];
|
||||
|
||||
self.isSetup = YES;
|
||||
}
|
||||
|
||||
#pragma mark - System Contact Fetching
|
||||
@ -240,8 +232,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
{
|
||||
OWSAssertDebug(contacts);
|
||||
OWSAssertDebug(completion);
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
|
||||
dispatch_async(self.intersectionQueue, ^{
|
||||
__block BOOL isFullIntersection = YES;
|
||||
@ -436,7 +426,8 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
// Retry with exponential backoff.
|
||||
//
|
||||
// TODO: Abort if another contact intersection succeeds in the meantime.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)),
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self intersectContacts:phoneNumbers
|
||||
retryDelaySeconds:retryDelaySeconds * 2.0
|
||||
success:successParameter
|
||||
@ -445,7 +436,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
};
|
||||
OWSContactDiscoveryTask *discoveryTask = [[OWSContactDiscoveryTask alloc] initWithPhoneNumbers:phoneNumbers];
|
||||
[discoveryTask performAtQoS:QOS_CLASS_USER_INITIATED
|
||||
callbackQueue:dispatch_get_main_queue()
|
||||
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
success:success
|
||||
failure:failure];
|
||||
}
|
||||
@ -456,107 +447,86 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
shouldClearStaleCache:(BOOL)shouldClearStaleCache
|
||||
{
|
||||
dispatch_async(self.intersectionQueue, ^{
|
||||
NSMutableArray<Contact *> *allContacts = [contacts mutableCopy];
|
||||
NSMutableDictionary<NSString *, Contact *> *allContactsMap = [NSMutableDictionary new];
|
||||
for (Contact *contact in contacts) {
|
||||
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
|
||||
NSString *phoneNumberE164 = phoneNumber.toE164;
|
||||
__block ContactsMaps *contactsMaps;
|
||||
DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) {
|
||||
NSString *_Nullable localNumber = [self.tsAccountManager localNumberWithTransaction:transaction];
|
||||
contactsMaps = [ContactsMaps buildWithContacts:contacts localNumber:localNumber];
|
||||
[self.contactsState setContactsMaps:contactsMaps localNumber:localNumber transaction:transaction];
|
||||
});
|
||||
|
||||
// Ignore any system contact records for the local contact.
|
||||
// For the local user we never want to show the avatar /
|
||||
// name that you have entered for yourself in your system
|
||||
// contacts. Instead, we always want to display your profile
|
||||
// name and avatar.
|
||||
BOOL isLocalContact = [phoneNumberE164 isEqualToString:TSAccountManager.localNumber];
|
||||
if (isLocalContact) {
|
||||
[allContacts removeObject:contact];
|
||||
} else if (phoneNumberE164.length > 0) {
|
||||
allContactsMap[phoneNumberE164] = contact;
|
||||
}
|
||||
}
|
||||
if (SSKDebugFlags.internalLogging) {
|
||||
OWSLogInfo(@"Updating contacts: %lu, phoneNumberToContactMap: %lu",
|
||||
(unsigned long)contactsMaps.uniqueIdToContactMap.count,
|
||||
(unsigned long)contactsMaps.phoneNumberToContactMap.count);
|
||||
}
|
||||
|
||||
NSArray<Contact *> *sortedContacts = [allContacts
|
||||
sortedArrayUsingComparator:[Contact comparatorSortingNamesByFirstThenLast:self.shouldSortByGivenName]];
|
||||
[self.cnContactCache removeAllObjects];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:OWSContactsManagerContactsDidChangeNotification
|
||||
object:nil];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (SSKDebugFlags.internalLogging) {
|
||||
OWSLogInfo(@"sortedContacts: %lu, allContactsMap: %lu",
|
||||
(unsigned long)sortedContacts.count,
|
||||
(unsigned long)allContactsMap.count);
|
||||
[self intersectContacts:contactsMaps.allContacts
|
||||
isUserRequested:isUserRequested
|
||||
completion:^(NSError *_Nullable error) {
|
||||
if (error != nil) {
|
||||
OWSFailDebug(@"Error: %@", error);
|
||||
return;
|
||||
}
|
||||
self.allContacts = sortedContacts;
|
||||
self.allContactsMap = [allContactsMap copy];
|
||||
[self.cnContactCache removeAllObjects];
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:OWSContactsManagerContactsDidChangeNotification
|
||||
object:nil];
|
||||
|
||||
[self intersectContacts:allContacts
|
||||
isUserRequested:isUserRequested
|
||||
completion:^(NSError *_Nullable error) {
|
||||
if (error != nil) {
|
||||
OWSFailDebug(@"Error: %@", error);
|
||||
return;
|
||||
}
|
||||
[OWSContactsManager
|
||||
buildSignalAccountsForContacts:sortedContacts
|
||||
shouldClearStaleCache:shouldClearStaleCache
|
||||
completion:^(NSArray<SignalAccount *> *signalAccounts) {
|
||||
[self updateSignalAccounts:signalAccounts
|
||||
shouldSetHasLoadedContacts:didLoad];
|
||||
}];
|
||||
}];
|
||||
});
|
||||
[OWSContactsManager buildSignalAccountsForContacts:contactsMaps.allContacts
|
||||
shouldClearStaleCache:shouldClearStaleCache
|
||||
completion:^(NSArray<SignalAccount *> *signalAccounts) {
|
||||
[self updateSignalAccountsForSystemContactsFetch:signalAccounts
|
||||
shouldSetHasLoadedSystemContacts:didLoad];
|
||||
}];
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateSignalAccounts:(NSArray<SignalAccount *> *)signalAccounts
|
||||
shouldSetHasLoadedContacts:(BOOL)shouldSetHasLoadedContacts
|
||||
- (void)updateSignalAccountsForSystemContactsFetch:(NSArray<SignalAccount *> *)newSignalAccounts
|
||||
shouldSetHasLoadedSystemContacts:(BOOL)shouldSetHasLoadedSystemContacts
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
BOOL hadLoadedContacts = self.hasLoadedContacts;
|
||||
if (shouldSetHasLoadedContacts) {
|
||||
_hasLoadedContacts = YES;
|
||||
BOOL hadLoadedContacts = self.hasLoadedSystemContacts;
|
||||
if (shouldSetHasLoadedSystemContacts) {
|
||||
_hasLoadedSystemContacts = YES;
|
||||
}
|
||||
|
||||
if ([signalAccounts isEqual:self.signalAccounts]) {
|
||||
__block NSArray<SignalAccount *> *oldSortedSignalAccounts;
|
||||
__block NSArray<SignalAccount *> *newSortedSignalAccounts;
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
oldSortedSignalAccounts = [self sortedSignalAccountsWithTransaction:transaction];
|
||||
newSortedSignalAccounts = [self sortSignalAccounts:newSignalAccounts transaction:transaction];
|
||||
}];
|
||||
|
||||
if ([oldSortedSignalAccounts isEqual:newSortedSignalAccounts]) {
|
||||
OWSLogDebug(@"SignalAccounts unchanged.");
|
||||
self.isSetup = YES;
|
||||
|
||||
if (hadLoadedContacts != self.hasLoadedContacts) {
|
||||
|
||||
if (hadLoadedContacts != self.hasLoadedSystemContacts) {
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
object:nil];
|
||||
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray<SignalServiceAddress *> *allAddresses = [NSMutableArray new];
|
||||
for (SignalAccount *signalAccount in signalAccounts) {
|
||||
[allAddresses addObject:signalAccount.recipientAddress];
|
||||
NSArray<SignalAccount *> *sortedSignalAccounts = [self sortSignalAccountsWithSneakyTransaction:newSignalAccounts];
|
||||
[self.contactsState setSortedSignalAccounts:sortedSignalAccounts];
|
||||
|
||||
if (CurrentAppContext().isMainApp) {
|
||||
// Ensure all system contacts are in the profile whitelist.
|
||||
NSMutableArray<SignalServiceAddress *> *allAddresses = [NSMutableArray new];
|
||||
for (SignalAccount *signalAccount in newSignalAccounts) {
|
||||
[allAddresses addObject:signalAccount.recipientAddress];
|
||||
}
|
||||
[self.profileManagerImpl setContactAddresses:allAddresses];
|
||||
}
|
||||
|
||||
self.signalAccounts = [self sortSignalAccountsWithSneakyTransaction:signalAccounts];
|
||||
|
||||
[self.profileManagerImpl setContactAddresses:allAddresses];
|
||||
|
||||
self.isSetup = YES;
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (nullable NSString *)cachedContactNameForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
SignalAccount *_Nullable signalAccount = [self fetchSignalAccountForAddress:address];
|
||||
return [self cachedContactNameForAddress:address signalAccount:signalAccount];
|
||||
}
|
||||
|
||||
- (nullable NSString *)cachedContactNameForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
@ -570,30 +540,23 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
// if signalAccount is nil.
|
||||
phoneNumber = [self phoneNumberForAddress:address transaction:transaction];
|
||||
}
|
||||
return [self cachedContactNameForAddress:address signalAccount:signalAccount phoneNumber:phoneNumber];
|
||||
}
|
||||
|
||||
- (nullable NSString *)cachedContactNameForAddress:(SignalServiceAddress *)address
|
||||
signalAccount:(nullable SignalAccount *)signalAccount
|
||||
{
|
||||
NSString *_Nullable phoneNumber = nil;
|
||||
if (signalAccount == nil) {
|
||||
// cachedContactNameForAddress only needs the phone number
|
||||
// if signalAccount is nil.
|
||||
phoneNumber = [self phoneNumberForAddress:address];
|
||||
}
|
||||
return [self cachedContactNameForAddress:address signalAccount:signalAccount phoneNumber:phoneNumber];
|
||||
return [self cachedContactNameForAddress:address
|
||||
signalAccount:signalAccount
|
||||
phoneNumber:phoneNumber
|
||||
transaction:transaction];
|
||||
}
|
||||
|
||||
- (nullable NSString *)cachedContactNameForAddress:(SignalServiceAddress *)address
|
||||
signalAccount:(nullable SignalAccount *)signalAccount
|
||||
phoneNumber:(nullable NSString *)phoneNumber
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(address);
|
||||
|
||||
if (!signalAccount) {
|
||||
// search system contacts for no-longer-registered signal users, for which there will be no SignalAccount
|
||||
Contact *_Nullable nonSignalContact = self.allContactsMap[phoneNumber];
|
||||
Contact *_Nullable nonSignalContact = [self contactForPhoneNumber:phoneNumber
|
||||
transaction:transaction];
|
||||
if (!nonSignalContact) {
|
||||
return nil;
|
||||
}
|
||||
@ -624,27 +587,19 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
phoneNumber = [self phoneNumberForAddress:address transaction:transaction];
|
||||
}
|
||||
|
||||
return [self cachedContactNameComponentsForSignalAccount:signalAccount phoneNumber:phoneNumber];
|
||||
}
|
||||
|
||||
- (nullable NSPersonNameComponents *)cachedContactNameComponentsForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
SignalAccount *_Nullable signalAccount = [self fetchSignalAccountForAddress:address];
|
||||
NSString *_Nullable phoneNumber = nil;
|
||||
if (signalAccount == nil) {
|
||||
// We only need the phone number if signalAccount is nil.
|
||||
phoneNumber = [self phoneNumberForAddress:address];
|
||||
}
|
||||
|
||||
return [self cachedContactNameComponentsForSignalAccount:signalAccount phoneNumber:phoneNumber];
|
||||
return [self cachedContactNameComponentsForSignalAccount:signalAccount
|
||||
phoneNumber:phoneNumber
|
||||
transaction:transaction];
|
||||
}
|
||||
|
||||
- (nullable NSPersonNameComponents *)cachedContactNameComponentsForSignalAccount:(nullable SignalAccount *)signalAccount
|
||||
phoneNumber:(nullable NSString *)phoneNumber
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
if (!signalAccount) {
|
||||
// search system contacts for no-longer-registered signal users, for which there will be no SignalAccount
|
||||
Contact *_Nullable nonSignalContact = self.allContactsMap[phoneNumber];
|
||||
Contact *_Nullable nonSignalContact = [self.contactsState contactForPhoneNumber:phoneNumber
|
||||
transaction:transaction];
|
||||
if (!nonSignalContact) {
|
||||
return nil;
|
||||
}
|
||||
@ -688,20 +643,41 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
|
||||
#pragma mark - Whisper User Management
|
||||
|
||||
- (BOOL)isSystemContactWithPhoneNumberWithSneakyTransaction:(NSString *)phoneNumber
|
||||
{
|
||||
__block BOOL result;
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
result = [self isSystemContactWithPhoneNumber:phoneNumber transaction:transaction];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isSystemContactWithPhoneNumber:(NSString *)phoneNumber
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(phoneNumber.length > 0);
|
||||
|
||||
return self.allContactsMap[phoneNumber] != nil;
|
||||
return [self.contactsState contactForPhoneNumber:phoneNumber
|
||||
transaction:transaction] != nil;
|
||||
}
|
||||
|
||||
- (BOOL)isSystemContactWithAddressWithSneakyTransaction:(SignalServiceAddress *)address
|
||||
{
|
||||
__block BOOL result;
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
result = [self isSystemContactWithAddress:address transaction:transaction];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isSystemContactWithAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
NSString *phoneNumber = address.phoneNumber;
|
||||
NSString *_Nullable phoneNumber = address.phoneNumber;
|
||||
if (phoneNumber.length == 0) {
|
||||
return NO;
|
||||
}
|
||||
return [self isSystemContactWithPhoneNumber:phoneNumber];
|
||||
return [self isSystemContactWithPhoneNumber:phoneNumber transaction:transaction];
|
||||
}
|
||||
|
||||
- (BOOL)isSystemContactWithSignalAccount:(SignalServiceAddress *)address
|
||||
@ -719,11 +695,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return [self hasSignalAccountForAddress:address transaction:transaction];
|
||||
}
|
||||
|
||||
- (BOOL)hasNameInSystemContactsForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
return [self cachedContactNameForAddress:address].length > 0;
|
||||
}
|
||||
|
||||
- (BOOL)hasNameInSystemContactsForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
@ -772,11 +743,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
@"UNKNOWN_CONTACT_NAME", @"Displayed if for some reason we can't determine a contacts phone number *or* name");
|
||||
}
|
||||
|
||||
- (nullable NSString *)nameFromSystemContactsForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
return [self cachedContactNameForAddress:address];
|
||||
}
|
||||
|
||||
- (nullable NSString *)nameFromSystemContactsForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
@ -823,7 +789,8 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
|
||||
return self.unknownUserLabel;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Remove?
|
||||
- (NSString *)displayNameForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
OWSAssertDebug(address.isValid);
|
||||
@ -842,13 +809,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return NSLocalizedString(@"UNKNOWN_USER", @"Label indicating an unknown user.");
|
||||
}
|
||||
|
||||
- (NSString *_Nonnull)displayNameForSignalAccount:(SignalAccount *)signalAccount
|
||||
{
|
||||
OWSAssertDebug(signalAccount);
|
||||
|
||||
return [self displayNameForAddress:signalAccount.recipientAddress];
|
||||
}
|
||||
|
||||
- (NSString *)shortDisplayNameForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
@ -870,22 +830,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return [OWSFormat formatNameComponentsShort:nameComponents];
|
||||
}
|
||||
|
||||
- (nullable NSPersonNameComponents *)nameComponentsForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
OWSAssertDebug(address.isValid);
|
||||
|
||||
NSPersonNameComponents *_Nullable savedContactNameComponents = [self cachedContactNameComponentsForAddress:address];
|
||||
if (savedContactNameComponents) {
|
||||
return savedContactNameComponents;
|
||||
}
|
||||
|
||||
__block NSPersonNameComponents *_Nullable profileNameComponents;
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
profileNameComponents = [self.profileManagerImpl nameComponentsForAddress:address transaction:transaction];
|
||||
}];
|
||||
return profileNameComponents;
|
||||
}
|
||||
|
||||
- (nullable NSPersonNameComponents *)nameComponentsForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
@ -900,6 +844,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return [self.profileManagerImpl nameComponentsForAddress:address transaction:transaction];
|
||||
}
|
||||
|
||||
// TODO: Remove?
|
||||
- (nullable SignalAccount *)fetchSignalAccountForAddress:(SignalServiceAddress *)address
|
||||
{
|
||||
OWSAssertDebug(address);
|
||||
@ -939,6 +884,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return [self fetchSignalAccountForAddress:address transaction:transaction] != nil;
|
||||
}
|
||||
|
||||
// TODO: Remove?
|
||||
- (nullable NSData *)profileImageDataForAddressWithSneakyTransaction:(nullable SignalServiceAddress *)address
|
||||
{
|
||||
if (address == nil) {
|
||||
@ -981,38 +927,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return contact.comparableNameLastFirst;
|
||||
}
|
||||
|
||||
- (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount
|
||||
{
|
||||
NSString *_Nullable name = [self comparableNameForContact:signalAccount.contact];
|
||||
|
||||
if (name.length > 0) {
|
||||
return name;
|
||||
}
|
||||
|
||||
NSString *_Nullable phoneNumber = signalAccount.recipientPhoneNumber;
|
||||
if (phoneNumber != nil) {
|
||||
Contact *_Nullable contact = self.allContactsMap[phoneNumber];
|
||||
NSString *_Nullable comparableContactName = [self comparableNameForContact:contact];
|
||||
if (comparableContactName.length > 0) {
|
||||
return comparableContactName;
|
||||
}
|
||||
}
|
||||
|
||||
__block NSPersonNameComponents *_Nullable nameComponents;
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
nameComponents = [self nameComponentsForAddress:signalAccount.recipientAddress transaction:transaction];
|
||||
}];
|
||||
|
||||
if (nameComponents != nil && nameComponents.givenName.length > 0 && nameComponents.familyName.length > 0) {
|
||||
NSString *leftName = self.shouldSortByGivenName ? nameComponents.givenName : nameComponents.familyName;
|
||||
NSString *rightName = self.shouldSortByGivenName ? nameComponents.familyName : nameComponents.givenName;
|
||||
return [NSString stringWithFormat:@"%@\t%@", leftName, rightName];
|
||||
}
|
||||
|
||||
// Fall back to non-contact display name.
|
||||
return [self displayNameForSignalAccount:signalAccount];
|
||||
}
|
||||
|
||||
- (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
@ -1024,7 +938,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
|
||||
NSString *_Nullable phoneNumber = signalAccount.recipientPhoneNumber;
|
||||
if (phoneNumber != nil) {
|
||||
Contact *_Nullable contact = self.allContactsMap[phoneNumber];
|
||||
Contact *_Nullable contact = [self contactForPhoneNumber:phoneNumber transaction:transaction];
|
||||
NSString *_Nullable comparableContactName = [self comparableNameForContact:contact];
|
||||
if (comparableContactName.length > 0) {
|
||||
return comparableContactName;
|
||||
@ -1044,6 +958,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
|
||||
return [self displayNameForAddress:signalAccount.recipientAddress transaction:transaction];
|
||||
}
|
||||
|
||||
// TODO: Remove?
|
||||
- (BOOL)isKnownRegisteredUserWithSneakyTransaction:(SignalServiceAddress *)address
|
||||
{
|
||||
__block BOOL result;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
|
||||
// TODO: Should we use these caches in NSE?
|
||||
fileprivate extension OWSContactsManager {
|
||||
@ -566,7 +567,7 @@ extension OWSContactsManager {
|
||||
}
|
||||
|
||||
if let phoneNumber = self.phoneNumber(for: address, transaction: transaction),
|
||||
let contact = self.allContactsMap[phoneNumber],
|
||||
let contact = contactsState.contact(forPhoneNumber: phoneNumber, transaction: transaction),
|
||||
let cnContactId = contact.cnContactId,
|
||||
let avatarData = self.avatarData(forCNContactId: cnContactId),
|
||||
let validData = validateIfNecessary(avatarData) {
|
||||
@ -884,3 +885,93 @@ public extension Array where Element == SignalAccount {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension OWSContactsManager {
|
||||
|
||||
@objc
|
||||
public func unsortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount] {
|
||||
contactsState.unsortedSignalAccounts(transaction: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func unsortedSignalAccountsWithSneakyTransaction() -> [SignalAccount] {
|
||||
databaseStorage.read { transaction in
|
||||
unsortedSignalAccounts(transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
// Order respects the systems contact sorting preference.
|
||||
@objc
|
||||
public func sortedSignalAccounts(transaction: SDSAnyReadTransaction) -> [SignalAccount] {
|
||||
contactsState.sortedSignalAccounts(transaction: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func sortedSignalAccountsWithSneakyTransaction() -> [SignalAccount] {
|
||||
databaseStorage.read { transaction in
|
||||
sortedSignalAccounts(transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
private static func removeLocalContact(contacts: [Contact],
|
||||
transaction: SDSAnyReadTransaction) -> [Contact] {
|
||||
let localNumber: String? = tsAccountManager.localNumber(with: transaction)
|
||||
|
||||
var contactIdSet = Set<String>()
|
||||
return contacts.compactMap { contact in
|
||||
// Skip local contacts.
|
||||
func isLocalContact() -> Bool {
|
||||
for phoneNumber in contact.parsedPhoneNumbers {
|
||||
if phoneNumber.toE164() == localNumber {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
guard !isLocalContact() else {
|
||||
return nil
|
||||
}
|
||||
// De-deduplicate.
|
||||
guard !contactIdSet.contains(contact.uniqueId) else {
|
||||
return nil
|
||||
}
|
||||
contactIdSet.insert(contact.uniqueId)
|
||||
return contact
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func allUnsortedContacts(transaction: SDSAnyReadTransaction) -> [Contact] {
|
||||
Self.removeLocalContact(contacts: contactsState.allContacts(transaction: transaction),
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func allSortedContacts(transaction: SDSAnyReadTransaction) -> [Contact] {
|
||||
let contacts = (allUnsortedContacts(transaction: transaction) as NSArray)
|
||||
let comparator = Contact.comparatorSortingNames(byFirstThenLast: self.shouldSortByGivenName)
|
||||
return contacts.sortedArray(options: [], usingComparator: comparator) as! [Contact]
|
||||
}
|
||||
|
||||
@objc
|
||||
public func comparableNameForSignalAccountWithSneakyTransaction(_ signalAccount: SignalAccount) -> String {
|
||||
databaseStorage.read { transaction in
|
||||
self.comparableName(for: signalAccount, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func displayName(forSignalAccount signalAccount: SignalAccount,
|
||||
transaction: SDSAnyReadTransaction) -> String {
|
||||
self.displayName(for: signalAccount.recipientAddress, transaction: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func displayNameForSignalAccountWithSneakyTransaction(_ signalAccount: SignalAccount) -> String {
|
||||
databaseStorage.read { transaction in
|
||||
self.displayName(forSignalAccount: signalAccount, transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +287,8 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
|
||||
|
||||
- (AnyPromise *)syncAllContacts
|
||||
{
|
||||
return [self syncContactsForSignalAccounts:self.contactsManager.signalAccounts
|
||||
NSArray<SignalAccount *> *allSignalAccounts = [self.contactsManagerImpl unsortedSignalAccountsWithSneakyTransaction];
|
||||
return [self syncContactsForSignalAccounts:allSignalAccounts
|
||||
skipIfRedundant:NO
|
||||
debounce:NO
|
||||
isDurableSend:NO];
|
||||
@ -301,7 +302,9 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
|
||||
- (void)sendSyncContactsMessageIfNecessary
|
||||
{
|
||||
OWSAssertDebug(self.tsAccountManager.isRegisteredPrimaryDevice);
|
||||
[self syncContactsForSignalAccounts:self.contactsManager.signalAccounts
|
||||
|
||||
NSArray<SignalAccount *> *allSignalAccounts = [self.contactsManagerImpl unsortedSignalAccountsWithSneakyTransaction];
|
||||
[self syncContactsForSignalAccounts:allSignalAccounts
|
||||
skipIfRedundant:YES
|
||||
debounce:YES
|
||||
isDurableSend:NO];
|
||||
|
||||
@ -33,6 +33,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly, nullable) NSString *cnContactId;
|
||||
@property (nonatomic, readonly) BOOL isFromContactSync;
|
||||
|
||||
// This property should only be accessed from Swift.
|
||||
@property (nonatomic, readonly) NSDictionary<NSString *, NSString *> *phoneNumberNameMap;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
- (instancetype)initWithSystemContact:(CNContact *)cnContact;
|
||||
|
||||
@ -14,14 +14,6 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Contact ()
|
||||
|
||||
@property (nonatomic, readonly) NSDictionary<NSString *, NSString *> *phoneNumberNameMap;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation Contact
|
||||
|
||||
@synthesize comparableNameFirstLast = _comparableNameFirstLast;
|
||||
|
||||
38
SignalServiceKit/src/Contacts/Contact.swift
Normal file
38
SignalServiceKit/src/Contacts/Contact.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Contact {
|
||||
public func isEqualForCache(_ other: Contact) -> Bool {
|
||||
guard self.firstName == other.firstName,
|
||||
self.lastName == other.lastName,
|
||||
self.nickname == other.nickname,
|
||||
self.fullName == other.fullName,
|
||||
self.comparableNameFirstLast == other.comparableNameFirstLast,
|
||||
self.comparableNameLastFirst == other.comparableNameLastFirst,
|
||||
self.uniqueId == other.uniqueId,
|
||||
self.cnContactId == other.cnContactId,
|
||||
self.isFromContactSync == other.isFromContactSync,
|
||||
self.phoneNumberNameMap == other.phoneNumberNameMap else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore ordering of these properties.
|
||||
guard Set(self.userTextPhoneNumbers) == Set(other.userTextPhoneNumbers),
|
||||
Set(self.emails) == Set(other.emails) else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore ordering of this property,
|
||||
// Don't rely on equality of PhoneNumber.
|
||||
let parsedPhoneNumbersSelf = Set(self.parsedPhoneNumbers.compactMap { $0.toE164() })
|
||||
let parsedPhoneNumbersOther = Set(other.parsedPhoneNumbers.compactMap { $0.toE164() })
|
||||
guard parsedPhoneNumbersSelf == parsedPhoneNumbersOther else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -155,7 +155,7 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value);
|
||||
- (NSArray<NSString *> *)systemContactsWithSignalAccountPhoneNumbers;
|
||||
- (NSArray<NSString *> *)systemContactsWithSignalAccountPhoneNumbersWithTransaction:
|
||||
(SDSAnyReadTransaction *)transaction;
|
||||
- (NSArray<NSString *> *)systemContactPhoneNumbers;
|
||||
- (NSArray<NSString *> *)systemContactPhoneNumbersWithTransaction:(SDSAnyReadTransaction *)transaction;
|
||||
- (NSArray<NSString *> *)e164PhoneNumbers NS_SWIFT_NAME(e164PhoneNumbers());
|
||||
|
||||
@end
|
||||
|
||||
@ -561,12 +561,13 @@ NSString *NSStringForContactAddressType(OWSContactAddressType value)
|
||||
}]];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)systemContactPhoneNumbers
|
||||
- (NSArray<NSString *> *)systemContactPhoneNumbersWithTransaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
return [self.e164PhoneNumbers
|
||||
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId,
|
||||
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||
return [OWSContact.contactsManager isSystemContactWithPhoneNumber:recipientId];
|
||||
return [OWSContact.contactsManager isSystemContactWithPhoneNumber:recipientId
|
||||
transaction:transaction];
|
||||
}]];
|
||||
}
|
||||
|
||||
|
||||
@ -616,7 +616,7 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes
|
||||
});
|
||||
OWSLogDebug(@"built message: %@ plainTextData.length: %lu", [message class], (unsigned long)plaintext.length);
|
||||
|
||||
// 2. gather "ud sending access" using a single write transaction.
|
||||
// 2. Gather "ud sending access".
|
||||
NSMutableDictionary<SignalServiceAddress *, OWSUDSendingAccess *> *sendingAccessMap = [NSMutableDictionary new];
|
||||
if (senderCertificates != nil) {
|
||||
for (SignalServiceAddress *address in addresses) {
|
||||
|
||||
@ -493,7 +493,10 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
|
||||
case .everybody:
|
||||
senderCertificate = senderCertificates.defaultCert
|
||||
case .contactsOnly:
|
||||
if Self.contactsManager.isSystemContact(address: address) {
|
||||
let isSystemContact = databaseStorage.read { transaction in
|
||||
Self.contactsManager.isSystemContact(address: address, transaction: transaction)
|
||||
}
|
||||
if isSystemContact {
|
||||
senderCertificate = senderCertificates.defaultCert
|
||||
} else {
|
||||
senderCertificate = senderCertificates.uuidOnlyCert
|
||||
|
||||
@ -25,7 +25,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// - The address' UUID
|
||||
- (NSString *)displayNameForAddress:(SignalServiceAddress *)address;
|
||||
- (NSString *)displayNameForAddress:(SignalServiceAddress *)address transaction:(SDSAnyReadTransaction *)transaction;
|
||||
- (NSString *)displayNameForSignalAccount:(SignalAccount *)signalAccount;
|
||||
|
||||
/// Returns the user's nickname / first name, if supported by the name's locale.
|
||||
/// If we don't know the user's name components, falls back to displayNameForAddress:
|
||||
@ -41,7 +40,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (NSString *)shortDisplayNameForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
- (nullable NSPersonNameComponents *)nameComponentsForAddress:(SignalServiceAddress *)address;
|
||||
- (nullable NSPersonNameComponents *)nameComponentsForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
@ -49,16 +47,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (NSString *)displayNameForThreadWithSneakyTransaction:(TSThread *)thread
|
||||
NS_SWIFT_NAME(displayNameWithSneakyTransaction(thread:));
|
||||
|
||||
- (NSArray<SignalAccount *> *)signalAccounts;
|
||||
|
||||
- (BOOL)isSystemContactWithPhoneNumber:(NSString *)phoneNumber NS_SWIFT_NAME(isSystemContact(phoneNumber:));
|
||||
- (BOOL)isSystemContactWithAddress:(SignalServiceAddress *)address NS_SWIFT_NAME(isSystemContact(address:));
|
||||
- (BOOL)isSystemContactWithPhoneNumberWithSneakyTransaction:(NSString *)phoneNumber NS_SWIFT_NAME(isSystemContactWithSneakyTransaction(phoneNumber:));
|
||||
- (BOOL)isSystemContactWithPhoneNumber:(NSString *)phoneNumber
|
||||
transaction:(SDSAnyReadTransaction *)transaction NS_SWIFT_NAME(isSystemContact(phoneNumber:transaction:));
|
||||
- (BOOL)isSystemContactWithAddressWithSneakyTransaction:(SignalServiceAddress *)address
|
||||
NS_SWIFT_NAME(isSystemContactWithSneakyTransaction(address:));
|
||||
- (BOOL)isSystemContactWithAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction NS_SWIFT_NAME(isSystemContact(address:transaction:));
|
||||
- (BOOL)isSystemContactWithSignalAccount:(SignalServiceAddress *)address
|
||||
NS_SWIFT_NAME(isSystemContactWithSignalAccount(_:));
|
||||
- (BOOL)isSystemContactWithSignalAccount:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
NS_SWIFT_NAME(isSystemContactWithSignalAccount(_:transaction:));
|
||||
- (BOOL)hasNameInSystemContactsForAddress:(SignalServiceAddress *)address;
|
||||
- (BOOL)hasNameInSystemContactsForAddress:(SignalServiceAddress *)address
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
|
||||
@ -303,6 +303,8 @@ public class SDSKeyValueStore: NSObject {
|
||||
write(codingValue, forKey: key, transaction: transaction)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public func removeValue(forKey key: String, transaction: SDSAnyWriteTransaction) {
|
||||
write(nil, forKey: key, transaction: transaction)
|
||||
|
||||
@ -26,10 +26,6 @@ public class FakeContactsManager: NSObject, ContactsManagerProtocol {
|
||||
return "Short fake name"
|
||||
}
|
||||
|
||||
public func nameComponents(for address: SignalServiceAddress) -> PersonNameComponents? {
|
||||
return PersonNameComponents()
|
||||
}
|
||||
|
||||
public func nameComponents(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> PersonNameComponents? {
|
||||
return PersonNameComponents()
|
||||
}
|
||||
@ -42,11 +38,19 @@ public class FakeContactsManager: NSObject, ContactsManagerProtocol {
|
||||
return []
|
||||
}
|
||||
|
||||
public func isSystemContact(phoneNumber: String) -> Bool {
|
||||
public func isSystemContactWithSneakyTransaction(phoneNumber: String) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func isSystemContact(address: SignalServiceAddress) -> Bool {
|
||||
public func isSystemContact(phoneNumber: String, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func isSystemContactWithSneakyTransaction(address: SignalServiceAddress) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func isSystemContact(address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -58,10 +62,6 @@ public class FakeContactsManager: NSObject, ContactsManagerProtocol {
|
||||
return true
|
||||
}
|
||||
|
||||
public func hasNameInSystemContacts(for address: SignalServiceAddress) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func hasNameInSystemContacts(for address: SignalServiceAddress, transaction: SDSAnyReadTransaction) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ public class LRUCache<KeyType: Hashable & Equatable, ValueType> {
|
||||
return
|
||||
}
|
||||
guard maxSize > 0 else {
|
||||
Logger.warn("Using disabled cache.")
|
||||
Logger.verbose("Using disabled cache.")
|
||||
return
|
||||
}
|
||||
cache.setObject(value as AnyObject, forKey: key as AnyObject)
|
||||
|
||||
@ -19,8 +19,7 @@ public class LocalDevice: NSObject {
|
||||
ProcessInfo.processInfo.activeProcessorCount
|
||||
}
|
||||
|
||||
@objc
|
||||
public static var memoryUsage: String {
|
||||
public static var memoryUsageUInt64: UInt64? {
|
||||
let vmInfoExpectedSize = MemoryLayout<task_vm_info_data_t>.size / MemoryLayout<natural_t>.size
|
||||
var vmInfo = task_vm_info_data_t()
|
||||
var vmInfoSize = mach_msg_type_number_t(vmInfoExpectedSize)
|
||||
@ -37,9 +36,17 @@ public class LocalDevice: NSObject {
|
||||
guard kern == KERN_SUCCESS else {
|
||||
let errorString = String(cString: mach_error_string(kern), encoding: .ascii) ?? "Unknown error"
|
||||
owsFailDebug(errorString)
|
||||
return "Unknown"
|
||||
return nil
|
||||
}
|
||||
|
||||
return "\(vmInfo.phys_footprint)"
|
||||
return vmInfo.phys_footprint
|
||||
}
|
||||
|
||||
@objc
|
||||
public static var memoryUsage: String {
|
||||
guard let memoryUsageUInt64 = self.memoryUsageUInt64 else {
|
||||
return "Unknown"
|
||||
}
|
||||
return "\(memoryUsageUInt64)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2352,7 +2352,8 @@ public class GroupManager: NSObject {
|
||||
}
|
||||
|
||||
let isLocalAddress = groupUpdateSourceAddress.isLocalAddress
|
||||
let isSystemContact = contactsManager.isSystemContact(address: groupUpdateSourceAddress)
|
||||
let isSystemContact = contactsManager.isSystemContact(address: groupUpdateSourceAddress,
|
||||
transaction: transaction)
|
||||
let isUserInProfileWhitelist = profileManager.isUser(inProfileWhitelist: groupUpdateSourceAddress,
|
||||
transaction: transaction)
|
||||
let shouldAddToWhitelist = (isLocalAddress || isSystemContact || isUserInProfileWhitelist)
|
||||
|
||||
@ -237,9 +237,13 @@ class OWSUDManagerTest: SSKBaseTestSwift {
|
||||
}
|
||||
|
||||
let completed = self.expectation(description: "completed")
|
||||
udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict).done { senderCertificates in
|
||||
firstly {
|
||||
udManagerImpl.ensureSenderCertificates(certificateExpirationPolicy: .strict)
|
||||
}.done { senderCertificates in
|
||||
do {
|
||||
let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress, requireSyncAccess: false, senderCertificates: senderCertificates)!
|
||||
let sendingAccess = self.udManagerImpl.udSendingAccess(forAddress: bobRecipientAddress,
|
||||
requireSyncAccess: false,
|
||||
senderCertificates: senderCertificates)!
|
||||
XCTAssertEqual(.unknown, sendingAccess.udAccess.udAccessMode)
|
||||
XCTAssertFalse(sendingAccess.udAccess.isRandomKey)
|
||||
XCTAssertEqual(sendingAccess.senderCertificate.serialize(),
|
||||
|
||||
@ -54,7 +54,7 @@ typedef void (^BlockAlertCompletionBlock)(ActionSheetAction *action);
|
||||
fromViewController:(UIViewController *)fromViewController
|
||||
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
|
||||
{
|
||||
NSString *displayName = [self.contactsManager displayNameForSignalAccount:signalAccount];
|
||||
NSString *displayName = [self.contactsManagerImpl displayNameForSignalAccountWithSneakyTransaction:signalAccount];
|
||||
[self showBlockAddressesActionSheet:@[ signalAccount.recipientAddress ]
|
||||
displayName:displayName
|
||||
fromViewController:fromViewController
|
||||
@ -289,7 +289,7 @@ typedef void (^BlockAlertCompletionBlock)(ActionSheetAction *action);
|
||||
fromViewController:(UIViewController *)fromViewController
|
||||
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
|
||||
{
|
||||
NSString *displayName = [self.contactsManager displayNameForSignalAccount:signalAccount];
|
||||
NSString *displayName = [self.contactsManagerImpl displayNameForSignalAccountWithSneakyTransaction:signalAccount];
|
||||
[self showUnblockAddressesActionSheet:@[ signalAccount.recipientAddress ]
|
||||
displayName:displayName
|
||||
fromViewController:fromViewController
|
||||
|
||||
@ -421,7 +421,7 @@ public class FullTextSearcher: NSObject {
|
||||
if address.isLocalAddress {
|
||||
return omitLocalUser
|
||||
}
|
||||
if self.contactsManager.isSystemContact(address: address) {
|
||||
if self.contactsManager.isSystemContact(address: address, transaction: transaction) {
|
||||
return false
|
||||
}
|
||||
guard let thread = TSContactThread.getWithContactAddress(address, transaction: transaction) else {
|
||||
|
||||
@ -124,8 +124,8 @@ public class ContactShareViewModel: NSObject {
|
||||
}
|
||||
|
||||
@objc
|
||||
public func systemContactPhoneNumbers() -> [String] {
|
||||
return dbRecord.systemContactPhoneNumbers()
|
||||
public func systemContactPhoneNumbers(transaction: SDSAnyReadTransaction) -> [String] {
|
||||
return dbRecord.systemContactPhoneNumbers(with: transaction)
|
||||
}
|
||||
|
||||
@objc
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#import <SignalCoreKit/NSString+OWS.h>
|
||||
#import <SignalMessaging/Environment.h>
|
||||
#import <SignalMessaging/OWSProfileManager.h>
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalServiceKit/AppContext.h>
|
||||
#import <SignalServiceKit/Contact.h>
|
||||
#import <SignalServiceKit/PhoneNumber.h>
|
||||
@ -217,7 +218,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
{
|
||||
OWSAssertDebug(!CurrentAppContext().isNSE);
|
||||
|
||||
return self.contactsManagerImpl.hasLoadedContacts;
|
||||
return self.contactsManagerImpl.hasLoadedSystemContacts;
|
||||
}
|
||||
|
||||
- (void)updateContacts
|
||||
@ -227,24 +228,27 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSMutableDictionary<NSString *, SignalAccount *> *phoneNumberSignalAccountMap = [NSMutableDictionary new];
|
||||
NSMutableDictionary<NSUUID *, SignalAccount *> *uuidSignalAccountMap = [NSMutableDictionary new];
|
||||
NSMutableArray<SignalAccount *> *signalAccounts = [NSMutableArray new];
|
||||
|
||||
NSMutableArray<SignalAccount *> *accountsToProcess = [self.contactsManager.signalAccounts mutableCopy];
|
||||
|
||||
__block NSArray<SignalAccount *> *allSignalAccounts;
|
||||
__block NSArray<SignalServiceAddress *> *whitelistedAddresses;
|
||||
__block ContactsMaps *contactsMaps;
|
||||
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
allSignalAccounts = [self.contactsManagerImpl unsortedSignalAccountsWithTransaction:transaction];
|
||||
whitelistedAddresses = [self.profileManagerImpl allWhitelistedRegisteredAddressesWithTransaction:transaction];
|
||||
contactsMaps = [self.contactsManagerImpl contactsMapsWithTransaction:transaction];
|
||||
}];
|
||||
|
||||
NSMutableArray<SignalAccount *> *accountsToProcess = [allSignalAccounts mutableCopy];
|
||||
for (SignalServiceAddress *address in whitelistedAddresses) {
|
||||
if ([self.contactsManager isSystemContactWithAddress:address]) {
|
||||
if ([contactsMaps isSystemContactWithAddress:address]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[accountsToProcess addObject:[[SignalAccount alloc] initWithSignalServiceAddress:address]];
|
||||
}
|
||||
|
||||
NSMutableSet<SignalServiceAddress *> *addressSet = [NSMutableSet new];
|
||||
NSMutableArray<SignalAccount *> *signalAccounts = [NSMutableArray new];
|
||||
for (SignalAccount *signalAccount in accountsToProcess) {
|
||||
if ([addressSet containsObject:signalAccount.recipientAddress]) {
|
||||
OWSLogVerbose(@"Ignoring duplicate: %@", signalAccount.recipientAddress);
|
||||
@ -361,18 +365,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
NSMutableSet<Contact *> *nonSignalContactSet = [NSMutableSet new];
|
||||
__block NSArray<Contact *> *nonSignalContacts;
|
||||
|
||||
[self.databaseStorage
|
||||
asyncReadWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
for (Contact *contact in self.contactsManagerImpl.allContactsMap.allValues) {
|
||||
NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction];
|
||||
if (signalRecipients.count < 1) {
|
||||
[nonSignalContactSet addObject:contact];
|
||||
}
|
||||
[self.databaseStorage asyncReadWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
NSArray<Contact *> *contacts = [self.contactsManagerImpl allSortedContactsWithTransaction:transaction];
|
||||
for (Contact *contact in contacts) {
|
||||
NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction];
|
||||
if (signalRecipients.count < 1) {
|
||||
[nonSignalContactSet addObject:contact];
|
||||
}
|
||||
nonSignalContacts = [nonSignalContactSet.allObjects sortedArrayUsingComparator:^NSComparisonResult(
|
||||
Contact *_Nonnull left, Contact *_Nonnull right) { return [left.fullName compare:right.fullName]; }];
|
||||
}
|
||||
completion:^{ self.nonSignalContacts = nonSignalContacts; }];
|
||||
nonSignalContacts = [nonSignalContactSet.allObjects sortedArrayUsingComparator:^NSComparisonResult(
|
||||
Contact *_Nonnull left, Contact *_Nonnull right) { return [left.fullName compare:right.fullName]; }];
|
||||
}
|
||||
completion:^{ self.nonSignalContacts = nonSignalContacts; }];
|
||||
}
|
||||
|
||||
- (nullable NSArray<Contact *> *)nonSignalContacts
|
||||
@ -383,7 +387,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
if (!_nonSignalContacts) {
|
||||
NSMutableSet<Contact *> *nonSignalContacts = [NSMutableSet new];
|
||||
[self.databaseStorage readWithBlock:^(SDSAnyReadTransaction *transaction) {
|
||||
for (Contact *contact in self.contactsManagerImpl.allContactsMap.allValues) {
|
||||
NSArray<Contact *> *contacts = [self.contactsManagerImpl allSortedContactsWithTransaction:transaction];
|
||||
for (Contact *contact in contacts) {
|
||||
NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction];
|
||||
if (signalRecipients.count < 1) {
|
||||
[nonSignalContacts addObject:contact];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user