diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 308a963352..838080e639 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -214,6 +214,7 @@ 3496957221A301A100DCFE74 /* OWSBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956921A301A100DCFE74 /* OWSBackup.m */; }; 3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496956A21A301A100DCFE74 /* OWSBackupJob.m */; }; 3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */; }; + 3498A0A624DC81E100CA492C /* OWSContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3498A0A524DC81E100CA492C /* OWSContactsManager.swift */; }; 34995F1B2411838D00C70546 /* NewGroupMembersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34995F172411838C00C70546 /* NewGroupMembersViewController.swift */; }; 3499998122EF1E2100654932 /* SearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3499997E22EF1E2100654932 /* SearcherTest.swift */; }; 3499998222EF1E2100654932 /* GRDBFullTextSearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3499997F22EF1E2100654932 /* GRDBFullTextSearcherTest.swift */; }; @@ -1125,6 +1126,7 @@ 3496956B21A301A100DCFE74 /* OWSBackupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupAPI.swift; sourceTree = ""; }; 3496956C21A301A100DCFE74 /* OWSBackupImportJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupImportJob.h; sourceTree = ""; }; 3496956D21A301A100DCFE74 /* OWSBackupIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupIO.h; sourceTree = ""; }; + 3498A0A524DC81E100CA492C /* OWSContactsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSContactsManager.swift; sourceTree = ""; }; 34995F172411838C00C70546 /* NewGroupMembersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewGroupMembersViewController.swift; sourceTree = ""; }; 3499997E22EF1E2100654932 /* SearcherTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearcherTest.swift; sourceTree = ""; }; 3499997F22EF1E2100654932 /* GRDBFullTextSearcherTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRDBFullTextSearcherTest.swift; sourceTree = ""; }; @@ -2318,15 +2320,16 @@ 346129A11FD1F09100532771 /* contacts */ = { isa = PBXGroup; children = ( + 4C9D347E23689E06006A4307 /* IncomingContactSyncJobQueue.swift */, + 4C6E446822AEDDEE007982E6 /* NewAccountDiscovery.swift */, 346129A21FD1F09100532771 /* OWSContactsManager.h */, 346129A31FD1F09100532771 /* OWSContactsManager.m */, + 3498A0A524DC81E100CA492C /* OWSContactsManager.swift */, + 4C046AA6236148880035B234 /* OWSGroupSyncProcessingJobQueue.swift */, 34612A041FD7238500532771 /* OWSSyncManager.h */, 34612A051FD7238500532771 /* OWSSyncManager.m */, 885C354F2370DFD50004BA35 /* OWSSyncManager.swift */, 346129AE1FD1F5D900532771 /* SystemContactsFetcher.swift */, - 4C6E446822AEDDEE007982E6 /* NewAccountDiscovery.swift */, - 4C046AA6236148880035B234 /* OWSGroupSyncProcessingJobQueue.swift */, - 4C9D347E23689E06006A4307 /* IncomingContactSyncJobQueue.swift */, ); path = contacts; sourceTree = ""; @@ -4611,6 +4614,7 @@ 34AC0A18211B39EA00997B47 /* TappableStackView.swift in Sources */, 346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */, 349C3637233D198300D52012 /* LaunchJobs.swift in Sources */, + 3498A0A624DC81E100CA492C /* OWSContactsManager.swift in Sources */, 8827004C232071C500F01C46 /* OWSWindow.swift in Sources */, 8809CE8122F534B200D38867 /* CustomKeyboard.swift in Sources */, 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */, diff --git a/SignalMessaging/Views/ContactsViewHelper.h b/SignalMessaging/Views/ContactsViewHelper.h index dd90757c98..3ff43b5ebe 100644 --- a/SignalMessaging/Views/ContactsViewHelper.h +++ b/SignalMessaging/Views/ContactsViewHelper.h @@ -26,6 +26,8 @@ NS_ASSUME_NONNULL_BEGIN @class CNContactViewController; @class SignalServiceAddress; +// TODO: This class does some expensive work and isn't stateful. +// It probably makes sense to make it a singleton. @interface ContactsViewHelper : NSObject @property (nonatomic, readonly, weak) id delegate; @@ -55,7 +57,6 @@ NS_ASSUME_NONNULL_BEGIN // is only safe to be called on the main thread. - (BOOL)isThreadBlocked:(TSThread *)thread; -// NOTE: This method uses a transaction. - (SignalServiceAddress *)localAddress; - (NSArray *)signalAccountsMatchingSearchString:(NSString *)searchText diff --git a/SignalMessaging/Views/ContactsViewHelper.m b/SignalMessaging/Views/ContactsViewHelper.m index f5344d0617..7877021c76 100644 --- a/SignalMessaging/Views/ContactsViewHelper.m +++ b/SignalMessaging/Views/ContactsViewHelper.m @@ -252,7 +252,7 @@ NS_ASSUME_NONNULL_BEGIN self.phoneNumberSignalAccountMap = [phoneNumberSignalAccountMap copy]; self.uuidSignalAccountMap = [uuidSignalAccountMap copy]; - self.signalAccounts = [signalAccounts sortedArrayUsingComparator:self.contactsManager.signalAccountComparator]; + self.signalAccounts = [self.contactsManager sortSignalAccountsWithSneakyTransaction:signalAccounts]; self.nonSignalContacts = nil; // Don't fire delegate "change" events during initialization. diff --git a/SignalMessaging/contacts/OWSContactsManager.h b/SignalMessaging/contacts/OWSContactsManager.h index 13940b5cb9..51ff5236f7 100644 --- a/SignalMessaging/contacts/OWSContactsManager.h +++ b/SignalMessaging/contacts/OWSContactsManager.h @@ -87,7 +87,6 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; /** * Used for sorting, respects system contacts name sort order preference. */ -- (NSComparisonResult (^)(SignalAccount *left, SignalAccount *right))signalAccountComparator; - (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount; - (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount transaction:(SDSAnyReadTransaction *)transaction; diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index bff2610f70..c3a8e8a94c 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -743,7 +743,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan [allAddresses addObject:signalAccount.recipientAddress]; } - self.signalAccounts = [signalAccounts sortedArrayUsingComparator:self.signalAccountComparator]; + self.signalAccounts = [self sortSignalAccountsWithSneakyTransaction:signalAccounts]; [self.profileManager setContactAddresses:allAddresses]; @@ -1292,66 +1292,6 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan return [self.profileManager profileAvatarForAddress:address transaction:transaction]; } -- (NSComparisonResult)compareSignalAccount:(SignalAccount *)left withSignalAccount:(SignalAccount *)right -{ - return self.signalAccountComparator(left, right); -} - -- (NSComparisonResult (^)(SignalAccount *left, SignalAccount *right))signalAccountComparator -{ - return ^NSComparisonResult(SignalAccount *left, SignalAccount *right) { - NSString *leftName = [self comparableNameForSignalAccount:left]; - NSString *rightName = [self comparableNameForSignalAccount:right]; - - NSComparisonResult nameComparison = [leftName caseInsensitiveCompare:rightName]; - if (nameComparison == NSOrderedSame) { - return [left.recipientAddress.stringForDisplay compare:right.recipientAddress.stringForDisplay]; - } - - return nameComparison; - }; -} - -- (NSArray *)sortSignalServiceAddresses:(NSArray *)addresses - transaction:(SDSAnyReadTransaction *)transaction -{ - return [addresses sortedArrayUsingComparator:[self signalServiceAddressComparatorWithTransaction:transaction]]; -} - -- (NSComparisonResult (^)(SignalServiceAddress *left, - SignalServiceAddress *right))signalServiceAddressComparatorWithTransaction:(SDSAnyReadTransaction *)transaction -{ - // We want to sort E164 phone numbers _after_ names. - NSCharacterSet *e164PrefixSet = [NSCharacterSet characterSetWithCharactersInString:@"+"]; - BOOL (^hasE164Prefix)(NSString *) = ^(NSString *string) { - if (string.length < 1) { - return NO; - } - unichar firstChar = [string characterAtIndex:0]; - return [e164PrefixSet characterIsMember:firstChar]; - }; - - return ^NSComparisonResult(SignalServiceAddress *left, SignalServiceAddress *right) { - NSString *leftName = [self comparableNameForAddress:left transaction:transaction]; - NSString *rightName = [self comparableNameForAddress:right transaction:transaction]; - - BOOL leftHasE164Prefix = hasE164Prefix(leftName); - BOOL rightHasE164Prefix = hasE164Prefix(rightName); - if (leftHasE164Prefix && !rightHasE164Prefix) { - return NSOrderedDescending; - } else if (!leftHasE164Prefix && rightHasE164Prefix) { - return NSOrderedAscending; - } - - NSComparisonResult nameComparison = [leftName caseInsensitiveCompare:rightName]; - if (nameComparison == NSOrderedSame) { - return [left.stringForDisplay compare:right.stringForDisplay]; - } - - return nameComparison; - }; -} - - (BOOL)shouldSortByGivenName { return [[CNContactsUserDefaults sharedDefaults] sortOrder] == CNContactSortOrderGivenName; diff --git a/SignalMessaging/contacts/OWSContactsManager.swift b/SignalMessaging/contacts/OWSContactsManager.swift new file mode 100644 index 0000000000..6036aff517 --- /dev/null +++ b/SignalMessaging/contacts/OWSContactsManager.swift @@ -0,0 +1,111 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public extension OWSContactsManager { + + // MARK: - Dependencies + + private var databaseStorage: SDSDatabaseStorage { + return SDSDatabaseStorage.shared + } + + // MARK: - + + func sortSignalAccountsWithSneakyTransaction(_ signalAccounts: [SignalAccount]) -> [SignalAccount] { + databaseStorage.read { transaction in + self.sortSignalAccounts(signalAccounts, transaction: transaction) + } + } + + func sortSignalAccounts(_ signalAccounts: [SignalAccount], + transaction: SDSAnyReadTransaction) -> [SignalAccount] { + + typealias SortableAccount = SortableValue + let sortableAccounts = signalAccounts.map { signalAccount in + return SortableAccount(value: signalAccount, + comparableName: self.comparableName(for: signalAccount, + transaction: transaction), + comparableAddress: signalAccount.recipientAddress.stringForDisplay) + } + return sort(sortableAccounts) + } + + // MARK: - + + func sortSignalServiceAddressesWithSneakyTransaction(_ addresses: [SignalServiceAddress]) -> [SignalServiceAddress] { + databaseStorage.read { transaction in + self.sortSignalServiceAddresses(addresses, transaction: transaction) + } + } + + func sortSignalServiceAddresses(_ addresses: [SignalServiceAddress], + transaction: SDSAnyReadTransaction) -> [SignalServiceAddress] { + + typealias SortableAddress = SortableValue + let sortableAddresses = addresses.map { address in + return SortableAddress(value: address, + comparableName: self.comparableName(for: address, + transaction: transaction), + comparableAddress: address.stringForDisplay) + } + return sort(sortableAddresses) + } +} + +// MARK: - + +private struct SortableValue { + let value: T + let comparableName: String + let comparableAddress: String + + init (value: T, comparableName: String, comparableAddress: String) { + self.value = value + self.comparableName = comparableName.lowercased() + self.comparableAddress = comparableAddress.lowercased() + } +} + +// MARK: - + +extension SortableValue: Equatable, Comparable { + + // MARK: - Equatable + + public static func == (lhs: SortableValue, rhs: SortableValue) -> Bool { + return (lhs.comparableName == rhs.comparableName && + lhs.comparableAddress == rhs.comparableAddress) + } + + // MARK: - Comparable + + public static func < (left: SortableValue, right: SortableValue) -> Bool { + // We want to sort E164 phone numbers _after_ names. + let leftHasE164Prefix = left.comparableName.hasPrefix("+") + let rightHasE164Prefix = right.comparableName.hasPrefix("+") + + if leftHasE164Prefix && !rightHasE164Prefix { + return false + } else if !leftHasE164Prefix && rightHasE164Prefix { + return true + } + + if left.comparableName != right.comparableName { + return left.comparableName < right.comparableName + } else { + return left.comparableAddress < right.comparableAddress + } + } +} + +// MARK: - + +fileprivate extension OWSContactsManager { + func sort(_ values: [SortableValue]) -> [T] { + return values.sorted().map { $0.value } + } +} diff --git a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h index e0d23e9f41..9e63c606d6 100644 --- a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h @@ -60,12 +60,6 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isSystemContactWithSignalAccount:(NSString *)phoneNumber; - (BOOL)hasNameInSystemContactsForAddress:(SignalServiceAddress *)address; -- (NSArray *)sortSignalServiceAddresses:(NSArray *)addresses - transaction:(SDSAnyReadTransaction *)transaction; - -- (NSComparisonResult)compareSignalAccount:(SignalAccount *)left - withSignalAccount:(SignalAccount *)right NS_SWIFT_NAME(compare(signalAccount:with:)); - - (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount transaction:(SDSAnyReadTransaction *)transaction;