Merge branch 'mkirk/search-profile-names'
This commit is contained in:
commit
9ea954bec2
|
@ -230,8 +230,6 @@
|
|||
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4574A5D51DD6704700C6B692 /* CallService.swift */; };
|
||||
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */; };
|
||||
45794E861E00620000066731 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45794E851E00620000066731 /* CallUIAdapter.swift */; };
|
||||
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
|
||||
45847E871E4283C30080EAB3 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45847E861E4283C30080EAB3 /* Intents.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; };
|
||||
45855F381D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; };
|
||||
|
@ -257,6 +255,8 @@
|
|||
45A6DAD71EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; };
|
||||
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; };
|
||||
45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; };
|
||||
45B72DDA1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */; };
|
||||
45B72DDB1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */; };
|
||||
45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
|
||||
45BB93391E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
|
||||
45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BD60811DE9547E00A8F436 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
|
@ -279,7 +279,6 @@
|
|||
45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; };
|
||||
45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; };
|
||||
45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; };
|
||||
45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */; };
|
||||
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; };
|
||||
45F170BC1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; };
|
||||
45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */; };
|
||||
|
@ -816,8 +815,6 @@
|
|||
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pastelog.h; sourceTree = "<group>"; };
|
||||
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pastelog.m; sourceTree = "<group>"; };
|
||||
45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = "<group>"; };
|
||||
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = "<group>"; };
|
||||
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = "<group>"; };
|
||||
45847E861E4283C30080EAB3 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
|
||||
45855F351D9498A40084F340 /* OWSContactAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactAvatarBuilder.h; sourceTree = "<group>"; };
|
||||
45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = "<group>"; };
|
||||
|
@ -849,6 +846,7 @@
|
|||
45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = "<group>"; };
|
||||
45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = "<group>"; };
|
||||
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearcher.swift; sourceTree = "<group>"; };
|
||||
45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = "<group>"; };
|
||||
45BD60811DE9547E00A8F436 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; };
|
||||
45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+OWS.swift"; sourceTree = "<group>"; };
|
||||
|
@ -866,7 +864,6 @@
|
|||
45E615151E8C590B0018AD52 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = "<group>"; };
|
||||
45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = "<group>"; };
|
||||
45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = "<group>"; };
|
||||
45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSessionTest.swift; sourceTree = "<group>"; };
|
||||
45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = "<group>"; };
|
||||
45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = "<group>"; };
|
||||
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = "<group>"; };
|
||||
|
@ -1644,8 +1641,6 @@
|
|||
children = (
|
||||
76EB040818170B33006006FC /* OWSContactsManager.h */,
|
||||
76EB040918170B33006006FC /* OWSContactsManager.m */,
|
||||
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */,
|
||||
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */,
|
||||
4542F0931EB9372700C7EE92 /* SystemContactsFetcher.swift */,
|
||||
);
|
||||
path = contact;
|
||||
|
@ -1725,6 +1720,7 @@
|
|||
76EB04FB18170B33006006FC /* Util.h */,
|
||||
45F170D51E315310003FC1F2 /* Weak.swift */,
|
||||
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */,
|
||||
45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */,
|
||||
);
|
||||
path = util;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1837,7 +1833,6 @@
|
|||
B660F6731C29867F00687D6E /* call */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */,
|
||||
456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */,
|
||||
);
|
||||
path = call;
|
||||
|
@ -2719,6 +2714,7 @@
|
|||
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
|
||||
34D1F0821F8678AA0066283D /* ConversationHeaderView.m in Sources */,
|
||||
340CB2241EAC155C0001CAA1 /* ContactsViewHelper.m in Sources */,
|
||||
45B72DDA1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */,
|
||||
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
|
||||
34CE88EC1F3237260098030F /* OWSProfileManager.m in Sources */,
|
||||
4542F0941EB9372700C7EE92 /* SystemContactsFetcher.swift in Sources */,
|
||||
|
@ -2773,7 +2769,6 @@
|
|||
D221A09A169C9E5E00537ABF /* main.m in Sources */,
|
||||
345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */,
|
||||
4585C4601ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m in Sources */,
|
||||
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */,
|
||||
B6258B331C29E2E60014138E /* NotificationsManager.m in Sources */,
|
||||
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */,
|
||||
34533F181EA8D2070006114F /* OWSAudioAttachmentPlayer.m in Sources */,
|
||||
|
@ -2928,11 +2923,9 @@
|
|||
458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */,
|
||||
452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */,
|
||||
451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */,
|
||||
45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */,
|
||||
456F6E231E24133500FD2210 /* Platform.swift in Sources */,
|
||||
4539B5871F79348F007141FF /* PushRegistrationManager.swift in Sources */,
|
||||
4504493A1F45EE7D002D1ADA /* NSString+OWS.m in Sources */,
|
||||
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */,
|
||||
45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
|
||||
45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */,
|
||||
B660F7561C29988E00687D6E /* PushManager.m in Sources */,
|
||||
|
@ -2978,6 +2971,7 @@
|
|||
45A6DAD71EBBF85500893231 /* ReminderView.swift in Sources */,
|
||||
B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */,
|
||||
45C0DC1F1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */,
|
||||
45B72DDB1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */,
|
||||
4505C2C01E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */,
|
||||
455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */,
|
||||
);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
//
|
||||
|
||||
#import "ContactsViewHelper.h"
|
||||
#import "ContactTableViewCell.h"
|
||||
#import "Environment.h"
|
||||
#import "NSString+OWS.h"
|
||||
#import "OWSProfileManager.h"
|
||||
|
@ -31,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic) BOOL shouldNotifyDelegateOfUpdatedContacts;
|
||||
@property (nonatomic) BOOL hasUpdatedContactsAtLeastOnce;
|
||||
@property (nonatomic) OWSProfileManager *profileManager;
|
||||
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -50,6 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
_blockingManager = [OWSBlockingManager sharedManager];
|
||||
_blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers];
|
||||
_conversationSearcher = ConversationSearcher.shared;
|
||||
|
||||
_contactsManager = [Environment getCurrent].contactsManager;
|
||||
_profileManager = [OWSProfileManager sharedManager];
|
||||
|
@ -173,37 +174,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)doesSignalAccount:(SignalAccount *)signalAccount matchSearchTerm:(NSString *)searchTerm
|
||||
{
|
||||
OWSAssert(signalAccount);
|
||||
OWSAssert(searchTerm.length > 0);
|
||||
|
||||
if ([signalAccount.contact.fullName.lowercaseString containsString:searchTerm.lowercaseString]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSString *asPhoneNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
|
||||
if (asPhoneNumber.length > 0 && [signalAccount.recipientId containsString:asPhoneNumber]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)doesSignalAccount:(SignalAccount *)signalAccount matchSearchTerms:(NSArray<NSString *> *)searchTerms
|
||||
{
|
||||
OWSAssert(signalAccount);
|
||||
OWSAssert(searchTerms.count > 0);
|
||||
|
||||
for (NSString *searchTerm in searchTerms) {
|
||||
if (![self doesSignalAccount:signalAccount matchSearchTerm:searchTerm]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)searchTermsForSearchString:(NSString *)searchText
|
||||
{
|
||||
return [[[searchText ows_stripped]
|
||||
|
@ -216,17 +186,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (NSArray<SignalAccount *> *)signalAccountsMatchingSearchString:(NSString *)searchText
|
||||
{
|
||||
NSArray<NSString *> *searchTerms = [self searchTermsForSearchString:searchText];
|
||||
|
||||
if (searchTerms.count < 1) {
|
||||
return self.signalAccounts;
|
||||
}
|
||||
|
||||
return [self.signalAccounts
|
||||
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount,
|
||||
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||
return [self doesSignalAccount:signalAccount matchSearchTerms:searchTerms];
|
||||
}]];
|
||||
return [self.conversationSearcher filterSignalAccounts:self.signalAccounts withSearchText:searchText];
|
||||
}
|
||||
|
||||
- (BOOL)doesContact:(Contact *)contact matchSearchTerm:(NSString *)searchTerm
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#import "Environment.h"
|
||||
#import "NewGroupViewController.h"
|
||||
#import "NewNonContactConversationViewController.h"
|
||||
#import "OWSContactsSearcher.h"
|
||||
#import "OWSTableViewController.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "UIColor+OWS.h"
|
||||
|
@ -45,6 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
MFMessageComposeViewControllerDelegate>
|
||||
|
||||
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
|
||||
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
|
||||
|
||||
@property (nonatomic, readonly) UIView *noSignalContactsView;
|
||||
|
||||
|
@ -77,6 +77,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
self.view.backgroundColor = UIColor.whiteColor;
|
||||
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
|
||||
_conversationSearcher = [ConversationSearcher shared];
|
||||
_nonContactAccountSet = [NSMutableSet set];
|
||||
_collation = [UILocalizedIndexedCollation currentCollation];
|
||||
|
||||
|
@ -630,35 +631,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (NSArray<TSGroupThread *> *)filteredGroupThreads
|
||||
{
|
||||
AnySearcher *searcher = [[AnySearcher alloc] initWithIndexer:^NSString * _Nonnull(id _Nonnull obj) {
|
||||
if (![obj isKindOfClass:[TSGroupThread class]]) {
|
||||
OWSFail(@"unexpected item in searcher");
|
||||
return @"";
|
||||
}
|
||||
TSGroupThread *groupThread = (TSGroupThread *)obj;
|
||||
NSString *groupName = groupThread.groupModel.groupName;
|
||||
NSMutableString *groupMemberNames = [NSMutableString new];
|
||||
for (NSString *recipientId in groupThread.groupModel.groupMemberIds) {
|
||||
NSString *contactName = [self.contactsViewHelper.contactsManager displayNameForPhoneIdentifier:recipientId];
|
||||
[groupMemberNames appendFormat:@" %@", contactName];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%@ %@", groupName, groupMemberNames];
|
||||
}];
|
||||
|
||||
NSMutableArray<TSGroupThread *> *matchingThreads = [NSMutableArray new];
|
||||
NSMutableArray<TSGroupThread *> *groupThreads = [NSMutableArray new];
|
||||
[TSGroupThread enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
if (![obj isKindOfClass:[TSGroupThread class]]) {
|
||||
// group and contact threads are in the same collection.
|
||||
return;
|
||||
}
|
||||
TSGroupThread *groupThread = (TSGroupThread *)obj;
|
||||
if ([searcher item:groupThread doesMatchQuery:self.searchBar.text]) {
|
||||
[matchingThreads addObject:groupThread];
|
||||
}
|
||||
[groupThreads addObject:groupThread];
|
||||
}];
|
||||
|
||||
return [matchingThreads copy];
|
||||
return [self.conversationSearcher filterGroupThreads:groupThreads withSearchText:self.searchBar.text];
|
||||
}
|
||||
|
||||
#pragma mark - No Contacts Mode
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
#import "Environment.h"
|
||||
#import "NSString+OWS.h"
|
||||
#import "OWSContactsManager.h"
|
||||
#import "OWSContactsSearcher.h"
|
||||
#import "OWSTableViewController.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "ThreadViewHelper.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import "UIFont+OWS.h"
|
||||
|
@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
|
||||
|
||||
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
|
||||
@property (nonatomic, readonly) ThreadViewHelper *threadViewHelper;
|
||||
|
||||
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
|
||||
|
@ -54,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
|
||||
_conversationSearcher = ConversationSearcher.shared;
|
||||
_threadViewHelper = [ThreadViewHelper new];
|
||||
_threadViewHelper.delegate = self;
|
||||
|
||||
|
@ -131,11 +132,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
__weak SelectThreadViewController *weakSelf = self;
|
||||
ContactsViewHelper *helper = self.contactsViewHelper;
|
||||
OWSTableContents *contents = [OWSTableContents new];
|
||||
OWSTableSection *section = [OWSTableSection new];
|
||||
|
||||
// Threads
|
||||
// Existing threads are listed first, ordered by most recently active
|
||||
OWSTableSection *recentChatsSection = [OWSTableSection new];
|
||||
recentChatsSection.headerTitle = NSLocalizedString(
|
||||
@"SELECT_THREAD_TABLE_RECENT_CHATS_TITLE", @"Table section header for recently active conversations");
|
||||
for (TSThread *thread in [self filteredThreadsWithSearchText]) {
|
||||
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
[recentChatsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
SelectThreadViewController *strongSelf = weakSelf;
|
||||
OWSCAssert(strongSelf);
|
||||
|
||||
|
@ -145,16 +148,23 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[cell configureWithThread:thread contactsManager:helper.contactsManager];
|
||||
return cell;
|
||||
}
|
||||
customRowHeight:[ContactTableViewCell rowHeight]
|
||||
actionBlock:^{
|
||||
[weakSelf.delegate threadWasSelected:thread];
|
||||
}]];
|
||||
customRowHeight:[ContactTableViewCell rowHeight]
|
||||
actionBlock:^{
|
||||
[weakSelf.delegate threadWasSelected:thread];
|
||||
}]];
|
||||
}
|
||||
|
||||
// Contacts
|
||||
if (recentChatsSection.itemCount > 0) {
|
||||
[contents addSection:recentChatsSection];
|
||||
}
|
||||
|
||||
// Contacts who don't yet have a thread are listed last
|
||||
OWSTableSection *otherContactsSection = [OWSTableSection new];
|
||||
otherContactsSection.headerTitle = NSLocalizedString(
|
||||
@"SELECT_THREAD_TABLE_OTHER_CHATS_TITLE", @"Table section header for conversations you haven't recently used.");
|
||||
NSArray<SignalAccount *> *filteredSignalAccounts = [self filteredSignalAccountsWithSearchText];
|
||||
for (SignalAccount *signalAccount in filteredSignalAccounts) {
|
||||
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
[otherContactsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
SelectThreadViewController *strongSelf = weakSelf;
|
||||
OWSCAssert(strongSelf);
|
||||
|
||||
|
@ -169,19 +179,24 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[cell configureWithSignalAccount:signalAccount contactsManager:helper.contactsManager];
|
||||
return cell;
|
||||
}
|
||||
customRowHeight:[ContactTableViewCell rowHeight]
|
||||
actionBlock:^{
|
||||
[weakSelf signalAccountWasSelected:signalAccount];
|
||||
}]];
|
||||
customRowHeight:[ContactTableViewCell rowHeight]
|
||||
actionBlock:^{
|
||||
[weakSelf signalAccountWasSelected:signalAccount];
|
||||
}]];
|
||||
}
|
||||
|
||||
if (section.itemCount < 1) {
|
||||
[section
|
||||
if (otherContactsSection.itemCount > 0) {
|
||||
[contents addSection:otherContactsSection];
|
||||
}
|
||||
|
||||
if (recentChatsSection.itemCount + otherContactsSection.itemCount < 1) {
|
||||
OWSTableSection *emptySection = [OWSTableSection new];
|
||||
[emptySection
|
||||
addItem:[OWSTableItem
|
||||
softCenterLabelItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_NO_CONTACTS",
|
||||
@"A label that indicates the user has no Signal contacts.")]];
|
||||
[contents addSection:emptySection];
|
||||
}
|
||||
[contents addSection:section];
|
||||
|
||||
self.tableViewController.contents = contents;
|
||||
}
|
||||
|
@ -222,28 +237,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (NSArray<TSThread *> *)filteredThreadsWithSearchText
|
||||
{
|
||||
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
|
||||
|
||||
NSString *searchTerm = [[self.searchBar text] ows_stripped];
|
||||
|
||||
if ([searchTerm isEqualToString:@""]) {
|
||||
return threads;
|
||||
}
|
||||
|
||||
NSString *formattedNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
|
||||
|
||||
NSMutableArray *result = [NSMutableArray new];
|
||||
for (TSThread *thread in threads) {
|
||||
if ([thread.name containsString:searchTerm]) {
|
||||
[result addObject:thread];
|
||||
} else if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||
if (formattedNumber.length > 0 && [contactThread.contactIdentifier containsString:formattedNumber]) {
|
||||
[result addObject:thread];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return [self.conversationSearcher filterThreads:self.threadViewHelper.threads withSearchText:searchTerm];
|
||||
}
|
||||
|
||||
- (NSArray<SignalAccount *> *)filteredSignalAccountsWithSearchText
|
||||
|
@ -259,10 +255,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
}
|
||||
|
||||
NSString *searchString = [self.searchBar text];
|
||||
NSString *searchString = self.searchBar.text;
|
||||
NSArray<SignalAccount *> *matchingAccounts =
|
||||
[self.contactsViewHelper signalAccountsMatchingSearchString:searchString];
|
||||
|
||||
ContactsViewHelper *helper = self.contactsViewHelper;
|
||||
return [[helper signalAccountsMatchingSearchString:searchString]
|
||||
return [matchingAccounts
|
||||
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount,
|
||||
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||
return ![contactIdsToIgnore containsObject:signalAccount.recipientId];
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// OWSContactsSearcher.h
|
||||
// Signal
|
||||
//
|
||||
// Created by Michael Kirk on 6/27/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Contact.h"
|
||||
|
||||
@interface OWSContactsSearcher : NSObject
|
||||
|
||||
- (instancetype)initWithContacts:(NSArray<Contact *> *)contacts;
|
||||
- (NSArray<Contact *> *)filterWithString:(NSString *)string;
|
||||
|
||||
@end
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSContactsSearcher.h"
|
||||
#import "NSString+OWS.h"
|
||||
#import <SignalServiceKit/PhoneNumber.h>
|
||||
|
||||
@interface OWSContactsSearcher ()
|
||||
|
||||
@property (copy) NSArray<Contact *> *contacts;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSContactsSearcher
|
||||
|
||||
- (instancetype)initWithContacts:(NSArray<Contact *> *)contacts {
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_contacts = contacts;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<Contact *> *)filterWithString:(NSString *)string {
|
||||
NSString *searchTerm = [string ows_stripped];
|
||||
|
||||
if ([searchTerm isEqualToString:@""]) {
|
||||
return self.contacts;
|
||||
}
|
||||
|
||||
NSString *formattedNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
|
||||
|
||||
// TODO: This assumes there's a single search term.
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(fullName contains[c] %@) OR (ANY parsedPhoneNumbers.toE164 contains[c] %@)", searchTerm, formattedNumber];
|
||||
|
||||
return [self.contacts filteredArrayUsingPredicate:predicate];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
|
||||
@objc
|
||||
class ConversationSearcher: NSObject {
|
||||
|
||||
@objc
|
||||
public static let shared: ConversationSearcher = ConversationSearcher()
|
||||
override private init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc(filterThreads:withSearchText:)
|
||||
public func filterThreads(_ threads: [TSThread], searchText: String) -> [TSThread] {
|
||||
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
|
||||
return threads
|
||||
}
|
||||
|
||||
return threads.filter { thread in
|
||||
switch thread {
|
||||
case let groupThread as TSGroupThread:
|
||||
return self.groupThreadSearcher.matches(item: groupThread, query: searchText)
|
||||
case let contactThread as TSContactThread:
|
||||
return self.contactThreadSearcher.matches(item: contactThread, query: searchText)
|
||||
default:
|
||||
owsFail("Unexpected thread type: \(thread)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc(filterGroupThreads:withSearchText:)
|
||||
public func filterGroupThreads(_ groupThreads: [TSGroupThread], searchText: String) -> [TSGroupThread] {
|
||||
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
|
||||
return groupThreads
|
||||
}
|
||||
|
||||
return groupThreads.filter { groupThread in
|
||||
return self.groupThreadSearcher.matches(item: groupThread, query: searchText)
|
||||
}
|
||||
}
|
||||
|
||||
@objc(filterSignalAccounts:withSearchText:)
|
||||
public func filterSignalAccounts(_ signalAccounts: [SignalAccount], searchText: String) -> [SignalAccount] {
|
||||
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
|
||||
return signalAccounts
|
||||
}
|
||||
|
||||
return signalAccounts.filter { signalAccount in
|
||||
self.signalAccountSearcher.matches(item: signalAccount, query: searchText)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
// MARK: Searchers
|
||||
private lazy var groupThreadSearcher: Searcher<TSGroupThread> = Searcher { (groupThread: TSGroupThread) in
|
||||
let groupName = groupThread.groupModel.groupName
|
||||
let memberStrings = groupThread.groupModel.groupMemberIds.map { recipientId in
|
||||
self.indexingString(recipientId: recipientId)
|
||||
}.joined(separator: " ")
|
||||
|
||||
return "\(memberStrings) \(groupName ?? "")"
|
||||
}
|
||||
|
||||
private lazy var contactThreadSearcher: Searcher<TSContactThread> = Searcher { (contactThread: TSContactThread) in
|
||||
let recipientId = contactThread.contactIdentifier()
|
||||
return self.indexingString(recipientId: recipientId)
|
||||
}
|
||||
|
||||
private lazy var signalAccountSearcher: Searcher<SignalAccount> = Searcher { (signalAccount: SignalAccount) in
|
||||
let recipientId = signalAccount.recipientId
|
||||
return self.indexingString(recipientId: recipientId)
|
||||
}
|
||||
|
||||
private var contactsManager: OWSContactsManager {
|
||||
return Environment.getCurrent().contactsManager
|
||||
}
|
||||
|
||||
private func indexingString(recipientId: String) -> String {
|
||||
let contactName = contactsManager.displayName(forPhoneIdentifier: recipientId)
|
||||
let profileName = contactsManager.profileName(forRecipientId: recipientId)
|
||||
|
||||
return "\(recipientId) \(contactName) \(profileName ?? "")"
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import Foundation
|
|||
}
|
||||
}
|
||||
|
||||
// A generic searching class, configurable with an indexing block
|
||||
class Searcher<T> {
|
||||
|
||||
private let indexer: (T) -> String
|
||||
|
@ -36,7 +37,16 @@ class Searcher<T> {
|
|||
}
|
||||
|
||||
private func stem(string: String) -> [String] {
|
||||
return normalize(string: string).components(separatedBy: .whitespaces)
|
||||
var normalized = normalize(string: string)
|
||||
|
||||
// Remove any phone number formatting from the search terms
|
||||
let nonformattingScalars = normalized.unicodeScalars.lazy.filter {
|
||||
!CharacterSet.punctuationCharacters.contains($0)
|
||||
}
|
||||
|
||||
normalized = String(String.UnicodeScalarView(nonformattingScalars))
|
||||
|
||||
return normalized.components(separatedBy: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
private func normalize(string: String) -> String {
|
||||
|
|
|
@ -170,6 +170,8 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
|
|||
self.nameLabel.attributedText = attributedText;
|
||||
|
||||
if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||
self.recipientId = thread.contactIdentifier;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(otherUsersProfileDidChange:)
|
||||
name:kNSNotificationName_OtherUsersProfileDidChange
|
||||
|
@ -220,6 +222,7 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
|
|||
diameter:kContactTableViewCellAvatarSize
|
||||
contactsManager:contactsManager] build];
|
||||
}
|
||||
|
||||
- (void)updateProfileName
|
||||
{
|
||||
OWSContactsManager *contactsManager = self.contactsManager;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import XCTest
|
||||
import PromiseKit
|
||||
import SignalServiceKit
|
||||
|
||||
struct VerificationFailedError: Error { }
|
||||
struct FailedToGetRPRegistrationTokenError: Error { }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
import SignalServiceKit
|
||||
|
||||
/**
|
||||
* This is a brittle test, which will break if our layout changes.
|
||||
|
@ -23,7 +24,12 @@ class MesssagesBubblesSizeCalculatorTest: XCTestCase {
|
|||
func viewItemForText(_ text: String?) -> ConversationViewItem {
|
||||
let interaction = TSOutgoingMessage(timestamp: 0, in: thread, messageBody: text)
|
||||
interaction.save()
|
||||
let viewItem = ConversationViewItem(tsInteraction:interaction, isGroupThread:false)
|
||||
|
||||
var viewItem: ConversationViewItem!
|
||||
interaction.dbReadWriteConnection().readWrite { transaction in
|
||||
viewItem = ConversationViewItem(interaction: interaction, isGroupThread: false, transaction: transaction)
|
||||
}
|
||||
|
||||
viewItem.shouldShowDate = false
|
||||
viewItem.shouldHideRecipientStatus = true
|
||||
return viewItem
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#import <SignalServiceKit/TSAttachmentStream.h>
|
||||
#import <SignalServiceKit/TSOutgoingMessage.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
|
||||
@interface ConversationViewItem (Testing)
|
||||
|
||||
|
@ -64,8 +65,10 @@
|
|||
|
||||
OWSAssert([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
|
||||
|
||||
TSAttachmentStream *attachment = [[TSAttachmentStream alloc] initWithContentType:mimeType sourceFilename:nil];
|
||||
DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:filePath];
|
||||
TSAttachmentStream *attachment = [[TSAttachmentStream alloc] initWithContentType:mimeType
|
||||
byteCount:(UInt32)dataSource.dataLength
|
||||
sourceFilename:nil];
|
||||
BOOL success = [attachment writeDataSource:dataSource];
|
||||
OWSAssert(success);
|
||||
[attachment save];
|
||||
|
@ -75,7 +78,12 @@
|
|||
TSOutgoingMessage *message =
|
||||
[[TSOutgoingMessage alloc] initWithTimestamp:1 inThread:nil messageBody:nil attachmentIds:attachmentIds];
|
||||
[message save];
|
||||
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:message isGroupThread:NO];
|
||||
|
||||
__block ConversationViewItem *viewItem = nil;
|
||||
[TSYapDatabaseObject.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
viewItem = [[ConversationViewItem alloc] initWithInteraction:message isGroupThread:NO transaction:transaction];
|
||||
}];
|
||||
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright © 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import AVKit
|
||||
import WebRTC
|
||||
|
||||
/**
|
||||
* These tests are obtuse - they just assert the exact implementation of the methods. Normally I wouldn't include them,
|
||||
* but these methods make use of a header not included in the standard distribution of the WebRTC.framework. We've
|
||||
* included the header in our local project, and test the methods here to make sure that they are still available when
|
||||
* we upgrade the framework.
|
||||
*
|
||||
* If they are failing, it's possible the RTCAudioSession header, and our usage of it, need to be updated.
|
||||
*/
|
||||
class CallAudioSessionTest: XCTestCase {
|
||||
func testAudioSession() {
|
||||
|
||||
let rtcAudioSession = RTCAudioSession.sharedInstance()
|
||||
// Sanity Check
|
||||
XCTAssertFalse(rtcAudioSession.useManualAudio)
|
||||
|
||||
CallAudioSession().configure()
|
||||
XCTAssertTrue(rtcAudioSession.useManualAudio)
|
||||
XCTAssertFalse(rtcAudioSession.isAudioEnabled)
|
||||
|
||||
CallAudioSession().start()
|
||||
XCTAssertTrue(rtcAudioSession.useManualAudio)
|
||||
XCTAssertTrue(rtcAudioSession.isAudioEnabled)
|
||||
|
||||
CallAudioSession().stop()
|
||||
XCTAssertTrue(rtcAudioSession.useManualAudio)
|
||||
XCTAssertFalse(rtcAudioSession.isAudioEnabled)
|
||||
}
|
||||
}
|
|
@ -9,14 +9,15 @@ class SearcherTest: XCTestCase {
|
|||
struct TestCharacter {
|
||||
let name: String
|
||||
let description: String
|
||||
let phoneNumber: String?
|
||||
}
|
||||
|
||||
let smerdyakov = TestCharacter(name: "Pavel Fyodorovich Smerdyakov", description: "A rusty hue in the sky")
|
||||
let stinkingLizaveta = TestCharacter(name: "Stinking Lizaveta", description: "object of pity")
|
||||
let regularLizaveta = TestCharacter(name: "Lizaveta", description: "")
|
||||
let smerdyakov = TestCharacter(name: "Pavel Fyodorovich Smerdyakov", description: "A rusty hue in the sky", phoneNumber: nil)
|
||||
let stinkingLizaveta = TestCharacter(name: "Stinking Lizaveta", description: "object of pity", phoneNumber: "+13235555555")
|
||||
let regularLizaveta = TestCharacter(name: "Lizaveta", description: "", phoneNumber: "1 (415) 555-5555")
|
||||
|
||||
let indexer = { (character: TestCharacter) in
|
||||
return "\(character.name) \(character.description)"
|
||||
return "\(character.name) \(character.description) \(character.phoneNumber ?? "")"
|
||||
}
|
||||
|
||||
var searcher: Searcher<TestCharacter> {
|
||||
|
@ -57,4 +58,20 @@ class SearcherTest: XCTestCase {
|
|||
XCTAssert(searcher.matches(item: stinkingLizaveta, query: "Lizaveta St"))
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query: " Lizaveta St "))
|
||||
}
|
||||
|
||||
func testFormattingChars() {
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"323"))
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"1-323-555-5555"))
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"13235555555"))
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"+1-323"))
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza +1-323"))
|
||||
|
||||
// Sanity check, match both by names
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza"))
|
||||
XCTAssert(searcher.matches(item: regularLizaveta, query:"Liza"))
|
||||
|
||||
// Disambiguate the two Liza's by area code
|
||||
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza 323"))
|
||||
XCTAssertFalse(searcher.matches(item: regularLizaveta, query:"Liza 323"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1342,6 +1342,12 @@
|
|||
/* Label for 'select gif to attach' action sheet button */
|
||||
"SELECT_GIF_BUTTON" = "GIF";
|
||||
|
||||
/* Table section header for conversations you haven't recently used. */
|
||||
"SELECT_THREAD_TABLE_OTHER_CHATS_TITLE" = "Other Contacts";
|
||||
|
||||
/* Table section header for recently active conversations */
|
||||
"SELECT_THREAD_TABLE_RECENT_CHATS_TITLE" = "Recent Chats";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"SEND_AGAIN_BUTTON" = "Send Again";
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
// This should be redundant with the logic above.
|
||||
[logPathSet addObjectsFromArray:self.fileLogger.logFileManager.unsortedLogFilePaths];
|
||||
NSArray<NSString *> *logPaths = logPathSet.allObjects;
|
||||
return [logPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
return [logPaths sortedArrayUsingSelector:@selector((compare:))];
|
||||
}
|
||||
|
||||
- (void)wipeLogs
|
||||
|
|
|
@ -320,8 +320,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
|
|||
// Store the file in a subdirectory whose name is the uniqueId of this attachment,
|
||||
// to avoid collisions between multiple attachments with the same name.
|
||||
NSString *attachmentFolderPath = [folder stringByAppendingPathComponent:uniqueId];
|
||||
NSError *error = nil;
|
||||
BOOL attachmentFolderPathExists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentFolderPath];
|
||||
if (![OWSFileSystem ensureDirectoryExists:attachmentFolderPath]) {
|
||||
return nil;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue