Let users share imported files to a thread or contact of their choice.
// FREEBIE
This commit is contained in:
parent
3c7574a908
commit
6e36ce97a5
|
@ -7,6 +7,9 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
3400C7931EAF89CD008A8584 /* SendExternalFileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */; };
|
||||||
|
3400C7961EAF99F4008A8584 /* SelectThreadViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */; };
|
||||||
|
3400C7991EAFB772008A8584 /* ThreadViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3400C7981EAFB772008A8584 /* ThreadViewHelper.m */; };
|
||||||
341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */; };
|
341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */; };
|
||||||
34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
|
34330A5A1E7875FB00DF2FB9 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
|
||||||
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
|
34330A5C1E787A9800DF2FB9 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
|
||||||
|
@ -344,6 +347,12 @@
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1B5E7D6C9007F5E5761D79DD /* libPods-SignalTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignalTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
1B5E7D6C9007F5E5761D79DD /* libPods-SignalTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignalTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3400C7901EAF89CD008A8584 /* SendExternalFileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SendExternalFileViewController.h; sourceTree = "<group>"; };
|
||||||
|
3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SendExternalFileViewController.m; sourceTree = "<group>"; };
|
||||||
|
3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectThreadViewController.h; sourceTree = "<group>"; };
|
||||||
|
3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SelectThreadViewController.m; sourceTree = "<group>"; };
|
||||||
|
3400C7971EAFB772008A8584 /* ThreadViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadViewHelper.h; sourceTree = "<group>"; };
|
||||||
|
3400C7981EAFB772008A8584 /* ThreadViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadViewHelper.m; sourceTree = "<group>"; };
|
||||||
341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = "<group>"; };
|
341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = "<group>"; };
|
||||||
341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = "<group>"; };
|
341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = "<group>"; };
|
||||||
34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = "<group>"; };
|
34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = "<group>"; };
|
||||||
|
@ -894,6 +903,10 @@
|
||||||
34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */,
|
34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */,
|
||||||
34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */,
|
34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */,
|
||||||
34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */,
|
34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */,
|
||||||
|
3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */,
|
||||||
|
3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */,
|
||||||
|
3400C7901EAF89CD008A8584 /* SendExternalFileViewController.h */,
|
||||||
|
3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */,
|
||||||
34B3F8681E8DF1700035BE1A /* SettingsTableViewController.h */,
|
34B3F8681E8DF1700035BE1A /* SettingsTableViewController.h */,
|
||||||
34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */,
|
34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */,
|
||||||
34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */,
|
34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */,
|
||||||
|
@ -903,6 +916,8 @@
|
||||||
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
|
34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */,
|
||||||
34B3F86F1E8DF1700035BE1A /* SignalsViewController.h */,
|
34B3F86F1E8DF1700035BE1A /* SignalsViewController.h */,
|
||||||
34B3F8701E8DF1700035BE1A /* SignalsViewController.m */,
|
34B3F8701E8DF1700035BE1A /* SignalsViewController.m */,
|
||||||
|
3400C7971EAFB772008A8584 /* ThreadViewHelper.h */,
|
||||||
|
3400C7981EAFB772008A8584 /* ThreadViewHelper.m */,
|
||||||
34B3F8A01E8EA6040035BE1A /* ViewControllerUtils.h */,
|
34B3F8A01E8EA6040035BE1A /* ViewControllerUtils.h */,
|
||||||
34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */,
|
34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */,
|
||||||
);
|
);
|
||||||
|
@ -1988,6 +2003,7 @@
|
||||||
B62D53F71A23CCAD009AAF82 /* TSMessageAdapter.m in Sources */,
|
B62D53F71A23CCAD009AAF82 /* TSMessageAdapter.m in Sources */,
|
||||||
76EB063C18170B33006006FC /* NumberUtil.m in Sources */,
|
76EB063C18170B33006006FC /* NumberUtil.m in Sources */,
|
||||||
B6A3EB4B1A423B3800B2236B /* TSPhotoAdapter.m in Sources */,
|
B6A3EB4B1A423B3800B2236B /* TSPhotoAdapter.m in Sources */,
|
||||||
|
3400C7961EAF99F4008A8584 /* SelectThreadViewController.m in Sources */,
|
||||||
34B3F88F1E8DF1710035BE1A /* RegistrationViewController.m in Sources */,
|
34B3F88F1E8DF1710035BE1A /* RegistrationViewController.m in Sources */,
|
||||||
34B3F8901E8DF1710035BE1A /* SettingsTableViewController.m in Sources */,
|
34B3F8901E8DF1710035BE1A /* SettingsTableViewController.m in Sources */,
|
||||||
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
|
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
|
||||||
|
@ -2071,7 +2087,9 @@
|
||||||
451DE9FD1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */,
|
451DE9FD1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */,
|
||||||
45666F761D9BFE00008FE134 /* OWS100RemoveTSRecipientsMigration.m in Sources */,
|
45666F761D9BFE00008FE134 /* OWS100RemoveTSRecipientsMigration.m in Sources */,
|
||||||
34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */,
|
34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */,
|
||||||
|
3400C7931EAF89CD008A8584 /* SendExternalFileViewController.m in Sources */,
|
||||||
FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */,
|
FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */,
|
||||||
|
3400C7991EAFB772008A8584 /* ThreadViewHelper.m in Sources */,
|
||||||
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
|
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
|
||||||
341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */,
|
341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */,
|
||||||
34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */,
|
34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */,
|
||||||
|
|
|
@ -119,146 +119,11 @@
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeName</key>
|
|
||||||
<string>Signal File</string>
|
|
||||||
<key>CFBundleTypeIconFiles</key>
|
|
||||||
<array>
|
|
||||||
<string>Icon</string>
|
|
||||||
<string>Icon@2x</string>
|
|
||||||
<string>Icon@3x</string>
|
|
||||||
</array>
|
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>public.item</string>
|
|
||||||
<string>public.content</string>
|
|
||||||
<string>public.composite-content</string>
|
|
||||||
<string>public.message</string>
|
|
||||||
<string>public.contact</string>
|
|
||||||
<string>public.archive</string>
|
<string>public.archive</string>
|
||||||
<string>public.disk-image</string>
|
<string>public.content</string>
|
||||||
<string>public.data</string>
|
<string>public.data</string>
|
||||||
<string>public.directory</string>
|
|
||||||
<string>com.apple.resolvable</string>
|
|
||||||
<string>public.symlink</string>
|
|
||||||
<string>public.executable</string>
|
|
||||||
<string>com.apple.mount-point</string>
|
|
||||||
<string>com.apple.alias-file</string>
|
|
||||||
<string>com.apple.alias-record</string>
|
|
||||||
<string>com.apple.bookmark</string>
|
|
||||||
<string>public.url</string>
|
|
||||||
<string>public.file-url</string>
|
|
||||||
<string>public.text</string>
|
|
||||||
<string>public.plain-text</string>
|
|
||||||
<string>public.utf8-plain-text</string>
|
|
||||||
<string>public.utf16-external-plain-text</string>
|
|
||||||
<string>public.utf16-plain-text</string>
|
|
||||||
<string>public.delimited-values-text</string>
|
|
||||||
<string>public.comma-separated-values-text</string>
|
|
||||||
<string>public.tab-separated-values-text</string>
|
|
||||||
<string>public.utf8-tab-separated-values-text</string>
|
|
||||||
<string>public.rtf</string>
|
|
||||||
<string>public.html</string>
|
|
||||||
<string>public.xml</string>
|
|
||||||
<string>public.source-code</string>
|
|
||||||
<string>public.assembly-source</string>
|
|
||||||
<string>public.c-source</string>
|
|
||||||
<string>public.objective-c-source</string>
|
|
||||||
<string>public.swift-source</string>
|
|
||||||
<string>public.c-plus-plus-source</string>
|
|
||||||
<string>public.objective-c-plus-plus-source</string>
|
|
||||||
<string>public.c-header</string>
|
|
||||||
<string>public.c-plus-plus-header</string>
|
|
||||||
<string>com.sun.java-source</string>
|
|
||||||
<string>public.script</string>
|
|
||||||
<string>com.apple.applescript.text</string>
|
|
||||||
<string>com.apple.applescript.script</string>
|
|
||||||
<string>com.apple.applescript.script-bundle</string>
|
|
||||||
<string>com.netscape.javascript-source</string>
|
|
||||||
<string>public.shell-script</string>
|
|
||||||
<string>public.perl-script</string>
|
|
||||||
<string>public.python-script</string>
|
|
||||||
<string>public.ruby-script</string>
|
|
||||||
<string>public.php-script</string>
|
|
||||||
<string>public.json</string>
|
|
||||||
<string>com.apple.property-list</string>
|
|
||||||
<string>com.apple.xml-property-list</string>
|
|
||||||
<string>com.apple.binary-property-list</string>
|
|
||||||
<string>com.adobe.pdf</string>
|
|
||||||
<string>com.apple.rtfd</string>
|
|
||||||
<string>com.apple.flat-rtfd</string>
|
|
||||||
<string>com.apple.txn.text-multimedia-data</string>
|
|
||||||
<string>com.apple.webarchive</string>
|
|
||||||
<string>public.image</string>
|
|
||||||
<string>public.jpeg</string>
|
|
||||||
<string>public.jpeg-2000</string>
|
|
||||||
<string>public.tiff</string>
|
|
||||||
<string>com.apple.pict</string>
|
|
||||||
<string>com.compuserve.gif</string>
|
|
||||||
<string>public.png</string>
|
|
||||||
<string>com.apple.quicktime-image</string>
|
|
||||||
<string>com.apple.icns</string>
|
|
||||||
<string>com.microsoft.bmp</string>
|
|
||||||
<string>com.microsoft.ico</string>
|
|
||||||
<string>public.camera-raw-image</string>
|
|
||||||
<string>public.svg-image</string>
|
|
||||||
<string>com.apple.live-photo</string>
|
|
||||||
<string>public.audiovisual-content</string>
|
|
||||||
<string>public.movie</string>
|
|
||||||
<string>public.video</string>
|
|
||||||
<string>public.audio</string>
|
|
||||||
<string>com.apple.quicktime-movie</string>
|
|
||||||
<string>public.mpeg</string>
|
|
||||||
<string>public.mpeg-2-video</string>
|
|
||||||
<string>public.mpeg-2-transport-stream</string>
|
|
||||||
<string>public.mp3</string>
|
|
||||||
<string>public.mpeg-4</string>
|
|
||||||
<string>public.mpeg-4-audio</string>
|
|
||||||
<string>com.apple.protected-mpeg-4-audio</string>
|
|
||||||
<string>com.apple.protected-mpeg-4-video</string>
|
|
||||||
<string>public.avi</string>
|
|
||||||
<string>public.aiff-audio</string>
|
|
||||||
<string>com.microsoft.waveform-audio</string>
|
|
||||||
<string>public.midi-audio</string>
|
|
||||||
<string>public.playlist</string>
|
|
||||||
<string>public.m3u-playlist</string>
|
|
||||||
<string>public.folder</string>
|
|
||||||
<string>public.volume</string>
|
|
||||||
<string>com.apple.package</string>
|
|
||||||
<string>com.apple.bundle</string>
|
|
||||||
<string>com.apple.plugin</string>
|
|
||||||
<string>com.apple.metadata-importer</string>
|
|
||||||
<string>com.apple.quicklook-generator</string>
|
|
||||||
<string>com.apple.xpc-service</string>
|
|
||||||
<string>com.apple.framework</string>
|
|
||||||
<string>com.apple.application</string>
|
|
||||||
<string>com.apple.application-bundle</string>
|
|
||||||
<string>com.apple.application-file</string>
|
|
||||||
<string>public.unix-executable</string>
|
|
||||||
<string>com.microsoft.windows-executable</string>
|
|
||||||
<string>com.sun.java-class</string>
|
|
||||||
<string>com.sun.java-archive</string>
|
|
||||||
<string>com.apple.systempreference.prefpane</string>
|
|
||||||
<string>org.gnu.gnu-zip-archive</string>
|
|
||||||
<string>public.bzip2-archive</string>
|
|
||||||
<string>public.zip-archive</string>
|
|
||||||
<string>public.spreadsheet</string>
|
|
||||||
<string>public.presentation</string>
|
|
||||||
<string>public.database</string>
|
|
||||||
<string>public.vcard</string>
|
|
||||||
<string>public.to-do-item</string>
|
|
||||||
<string>public.calendar-event</string>
|
|
||||||
<string>public.email-message</string>
|
|
||||||
<string>com.apple.internet-location</string>
|
|
||||||
<string>com.apple.ink.inktext</string>
|
|
||||||
<string>public.font</string>
|
|
||||||
<string>public.bookmark</string>
|
|
||||||
<string>public.3d-content</string>
|
|
||||||
<string>com.rsa.pkcs-12</string>
|
|
||||||
<string>public.x509-certificate</string>
|
|
||||||
<string>org.idpf.epub-container</string>
|
|
||||||
<string>public.log</string>
|
|
||||||
<string>com.adobe.pdf</string>
|
|
||||||
<string>public.pdf</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>LSHandlerRank</key>
|
<key>LSHandlerRank</key>
|
||||||
<string>Alternate</string>
|
<string>Alternate</string>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#import "PropertyListPreferences.h"
|
#import "PropertyListPreferences.h"
|
||||||
#import "PushManager.h"
|
#import "PushManager.h"
|
||||||
#import "Release.h"
|
#import "Release.h"
|
||||||
|
#import "SendExternalFileViewController.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
#import "TSMessagesManager.h"
|
#import "TSMessagesManager.h"
|
||||||
#import "TSSocketManager.h"
|
#import "TSSocketManager.h"
|
||||||
|
@ -294,6 +295,22 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
DDLogInfo(@"Application opened with URL: %@", url);
|
DDLogInfo(@"Application opened with URL: %@", url);
|
||||||
|
|
||||||
|
[[TSAccountManager sharedInstance]
|
||||||
|
ifRegistered:YES
|
||||||
|
runAsync:^{
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
SendExternalFileViewController *viewController = [SendExternalFileViewController new];
|
||||||
|
viewController.attachment = attachment;
|
||||||
|
UINavigationController *navigationController =
|
||||||
|
[[UINavigationController alloc] initWithRootViewController:viewController];
|
||||||
|
[[[Environment getCurrent] signalsViewController]
|
||||||
|
presentTopLevelModalViewController:navigationController
|
||||||
|
animateDismissal:NO
|
||||||
|
animatePresentation:YES];
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
} else {
|
} else {
|
||||||
DDLogWarn(@"Application opened with an unknown URL scheme: %@", url.scheme);
|
DDLogWarn(@"Application opened with an unknown URL scheme: %@", url.scheme);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@class Contact;
|
||||||
@class OWSContactsManager;
|
@class OWSContactsManager;
|
||||||
|
|
||||||
typedef enum : NSUInteger { kArchiveState = 0, kInboxState = 1 } CellState;
|
typedef enum : NSUInteger { kArchiveState = 0, kInboxState = 1 } CellState;
|
||||||
|
@ -20,13 +21,23 @@ typedef enum : NSUInteger { kArchiveState = 0, kInboxState = 1 } CellState;
|
||||||
@property (nonatomic) IBOutlet UIView *contentContainerView;
|
@property (nonatomic) IBOutlet UIView *contentContainerView;
|
||||||
@property (nonatomic) IBOutlet UIView *messageCounter;
|
@property (nonatomic) IBOutlet UIView *messageCounter;
|
||||||
@property (nonatomic) NSString *threadId;
|
@property (nonatomic) NSString *threadId;
|
||||||
|
@property (nonatomic) NSString *contactId;
|
||||||
|
|
||||||
+ (instancetype)inboxTableViewCell;
|
+ (instancetype)inboxTableViewCell;
|
||||||
|
|
||||||
|
+ (CGFloat)rowHeight;
|
||||||
|
|
||||||
- (void)configureWithThread:(TSThread *)thread
|
- (void)configureWithThread:(TSThread *)thread
|
||||||
contactsManager:(OWSContactsManager *)contactsManager
|
contactsManager:(OWSContactsManager *)contactsManager
|
||||||
blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet;
|
blockedPhoneNumberSet:(NSSet<NSString *> *)blockedPhoneNumberSet;
|
||||||
|
|
||||||
|
// This method is used to present _possible_ threads - threads
|
||||||
|
// that will be created if this cell is selected.
|
||||||
|
- (void)configureWithContact:(Contact *)contact
|
||||||
|
recipientId:(NSString *)recipientId
|
||||||
|
contactsManager:(OWSContactsManager *)contactsManager
|
||||||
|
isBlocked:(BOOL)isBlocked;
|
||||||
|
|
||||||
- (void)animateDisappear;
|
- (void)animateDisappear;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#import "InboxTableViewCell.h"
|
#import "InboxTableViewCell.h"
|
||||||
#import "Environment.h"
|
#import "Environment.h"
|
||||||
#import "OWSAvatarBuilder.h"
|
#import "OWSAvatarBuilder.h"
|
||||||
|
#import "OWSContactAvatarBuilder.h"
|
||||||
#import "PropertyListPreferences.h"
|
#import "PropertyListPreferences.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
#import "TSContactThread.h"
|
#import "TSContactThread.h"
|
||||||
|
@ -25,12 +26,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface InboxTableViewCell ()
|
@interface InboxTableViewCell ()
|
||||||
|
|
||||||
@property NSUInteger unreadMessages;
|
@property (nonatomic) NSUInteger unreadMessages;
|
||||||
@property UIView *messagesBadge;
|
@property (nonatomic) UIView *messagesBadge;
|
||||||
@property UILabel *unreadLabel;
|
@property (nonatomic) UILabel *unreadLabel;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
@implementation InboxTableViewCell
|
@implementation InboxTableViewCell
|
||||||
|
|
||||||
+ (instancetype)inboxTableViewCell {
|
+ (instancetype)inboxTableViewCell {
|
||||||
|
@ -41,6 +44,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (CGFloat)rowHeight
|
||||||
|
{
|
||||||
|
return 72.f;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)initializeLayout {
|
- (void)initializeLayout {
|
||||||
self.selectionStyle = UITableViewCellSelectionStyleDefault;
|
self.selectionStyle = UITableViewCellSelectionStyleDefault;
|
||||||
}
|
}
|
||||||
|
@ -130,6 +138,52 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)configureWithContact:(Contact *)contact
|
||||||
|
recipientId:(NSString *)recipientId
|
||||||
|
contactsManager:(OWSContactsManager *)contactsManager
|
||||||
|
isBlocked:(BOOL)isBlocked
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
OWSAssert(contact);
|
||||||
|
OWSAssert(recipientId.length > 0);
|
||||||
|
OWSAssert(contactsManager);
|
||||||
|
|
||||||
|
NSString *name = contact.fullName;
|
||||||
|
self.threadId = recipientId;
|
||||||
|
NSMutableAttributedString *snippetText = [NSMutableAttributedString new];
|
||||||
|
if (isBlocked) {
|
||||||
|
// If thread is blocked, don't show a snippet or mute status.
|
||||||
|
[snippetText
|
||||||
|
appendAttributedString:[[NSAttributedString alloc]
|
||||||
|
initWithString:NSLocalizedString(@"HOME_VIEW_BLOCKED_CONTACT_CONVERSATION",
|
||||||
|
@"A label for conversations with blocked users.")
|
||||||
|
attributes:@{
|
||||||
|
NSFontAttributeName : [UIFont ows_mediumFontWithSize:12],
|
||||||
|
NSForegroundColorAttributeName : [UIColor ows_blackColor],
|
||||||
|
}]];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nameLabel.text = name;
|
||||||
|
self.snippetLabel.attributedText = snippetText;
|
||||||
|
self.contactPictureView.image = [UIImage imageNamed:@"empty-group-avatar"];
|
||||||
|
[UIUtil applyRoundedBorderToImageView:_contactPictureView];
|
||||||
|
|
||||||
|
self.separatorInset = UIEdgeInsetsMake(0, _contactPictureView.frame.size.width * 1.5f, 0, 0);
|
||||||
|
|
||||||
|
[self updateCellForUnreadMessage];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
UIImage *avatar = [[[OWSContactAvatarBuilder alloc] initWithContactId:recipientId
|
||||||
|
name:contact.fullName
|
||||||
|
contactsManager:contactsManager] build];
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if ([self.threadId isEqualToString:recipientId]) {
|
||||||
|
self.contactPictureView.image = avatar;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
- (void)updateCellForUnreadMessage {
|
- (void)updateCellForUnreadMessage {
|
||||||
_nameLabel.font = [UIFont ows_boldFontWithSize:14.0f];
|
_nameLabel.font = [UIFont ows_boldFontWithSize:14.0f];
|
||||||
_nameLabel.textColor = [UIColor ows_blackColor];
|
_nameLabel.textColor = [UIColor ows_blackColor];
|
||||||
|
|
|
@ -113,24 +113,10 @@ typedef enum : NSUInteger {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)pasteBoardHasText
|
|
||||||
{
|
|
||||||
if ([UIPasteboard generalPasteboard].numberOfItems < 1) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
NSIndexSet *itemSet = [NSIndexSet indexSetWithIndex:0];
|
|
||||||
NSSet<NSString *> *utiTypes =
|
|
||||||
[NSSet setWithArray:[[UIPasteboard generalPasteboard] pasteboardTypesForItemSet:itemSet][0]];
|
|
||||||
return ([utiTypes containsObject:(NSString *)kUTTypeText] || [utiTypes containsObject:(NSString *)kUTTypePlainText]
|
|
||||||
||
|
|
||||||
[utiTypes containsObject:(NSString *)kUTTypeUTF8PlainText] ||
|
|
||||||
[utiTypes containsObject:(NSString *)kUTTypeUTF16PlainText]);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)pasteBoardHasPossibleAttachment {
|
- (BOOL)pasteBoardHasPossibleAttachment {
|
||||||
// We don't want to load/convert images more than once so we
|
// We don't want to load/convert images more than once so we
|
||||||
// only do a cursory validation pass at this time.
|
// only do a cursory validation pass at this time.
|
||||||
return ([SignalAttachment pasteboardHasPossibleAttachment] && ![self pasteBoardHasText]);
|
return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteBoardHasText]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
|
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
|
||||||
|
|
|
@ -35,6 +35,8 @@ extern const CGFloat kOWSTable_DefaultCellHeight;
|
||||||
|
|
||||||
- (void)addItem:(OWSTableItem *)item;
|
- (void)addItem:(OWSTableItem *)item;
|
||||||
|
|
||||||
|
- (NSUInteger)itemCount;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
@ -69,8 +71,18 @@ typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)();
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
|
@protocol OWSTableViewControllerDelegate <NSObject>
|
||||||
|
|
||||||
|
- (void)tableViewDidScroll;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
@interface OWSTableViewController : UIViewController
|
@interface OWSTableViewController : UIViewController
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id<OWSTableViewControllerDelegate> delegate;
|
||||||
|
|
||||||
@property (nonatomic) OWSTableContents *contents;
|
@property (nonatomic) OWSTableContents *contents;
|
||||||
@property (nonatomic, readonly) UITableView *tableView;
|
@property (nonatomic, readonly) UITableView *tableView;
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,11 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
|
||||||
[_items addObject:item];
|
[_items addObject:item];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)itemCount
|
||||||
|
{
|
||||||
|
return _items.count;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
@ -171,19 +176,15 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
|
||||||
|
|
||||||
@implementation OWSTableViewController
|
@implementation OWSTableViewController
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
[self.navigationController.navigationBar setTranslucent:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadView
|
- (void)loadView
|
||||||
{
|
{
|
||||||
[super loadView];
|
[super loadView];
|
||||||
|
|
||||||
OWSAssert(self.contents);
|
OWSAssert(self.contents);
|
||||||
|
|
||||||
self.title = self.contents.title;
|
if (self.contents.title.length > 0) {
|
||||||
|
self.title = self.contents.title;
|
||||||
|
}
|
||||||
|
|
||||||
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
|
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
|
||||||
self.tableView.delegate = self;
|
self.tableView.delegate = self;
|
||||||
|
@ -196,6 +197,20 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
|
||||||
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kOWSTableCellIdentifier];
|
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kOWSTableCellIdentifier];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad
|
||||||
|
{
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
[self.navigationController.navigationBar setTranslucent:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillAppear:(BOOL)animated
|
||||||
|
{
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||||
|
}
|
||||||
|
|
||||||
- (OWSTableSection *)sectionForIndex:(NSInteger)sectionIndex
|
- (OWSTableSection *)sectionForIndex:(NSInteger)sectionIndex
|
||||||
{
|
{
|
||||||
OWSAssert(self.contents);
|
OWSAssert(self.contents);
|
||||||
|
@ -217,20 +232,27 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setContents:(OWSTableContents *)contents
|
||||||
|
{
|
||||||
|
OWSAssert(contents);
|
||||||
|
|
||||||
|
_contents = contents;
|
||||||
|
|
||||||
|
[self.tableView reloadData];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Table view data source
|
#pragma mark - Table view data source
|
||||||
|
|
||||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||||
{
|
{
|
||||||
OWSAssert(self.contents);
|
OWSAssert(self.contents);
|
||||||
|
|
||||||
OWSAssert(self.contents.sections.count > 0);
|
|
||||||
return (NSInteger) self.contents.sections.count;
|
return (NSInteger) self.contents.sections.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex
|
||||||
{
|
{
|
||||||
OWSTableSection *section = [self sectionForIndex:sectionIndex];
|
OWSTableSection *section = [self sectionForIndex:sectionIndex];
|
||||||
OWSAssert(section.items.count > 0);
|
OWSAssert(section.items);
|
||||||
return (NSInteger) section.items.count;
|
return (NSInteger) section.items.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +378,13 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
|
||||||
[self dismissViewControllerAnimated:YES completion:nil];
|
[self dismissViewControllerAnimated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - UIScrollViewDelegate
|
||||||
|
|
||||||
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||||
|
{
|
||||||
|
[self.delegate tableViewDidScroll];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
@class TSThread;
|
||||||
|
|
||||||
|
@protocol SelectThreadViewControllerDelegate <NSObject>
|
||||||
|
|
||||||
|
- (void)threadWasSelected:(TSThread *)thread;
|
||||||
|
|
||||||
|
- (BOOL)canSelectBlockedContact;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
@interface SelectThreadViewController : UIViewController
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id<SelectThreadViewControllerDelegate> delegate;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,428 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SelectThreadViewController.h"
|
||||||
|
#import "BlockListUIUtils.h"
|
||||||
|
#import "ContactTableViewCell.h"
|
||||||
|
#import "Environment.h"
|
||||||
|
#import "InboxTableViewCell.h"
|
||||||
|
#import "OWSContactsManager.h"
|
||||||
|
#import "OWSContactsSearcher.h"
|
||||||
|
#import "OWSTableViewController.h"
|
||||||
|
#import "ThreadViewHelper.h"
|
||||||
|
#import "UIColor+OWS.h"
|
||||||
|
#import "UIFont+OWS.h"
|
||||||
|
#import "UIView+OWS.h"
|
||||||
|
#import <SignalServiceKit/OWSBlockingManager.h>
|
||||||
|
#import <SignalServiceKit/TSAccountManager.h>
|
||||||
|
#import <SignalServiceKit/TSContactThread.h>
|
||||||
|
#import <SignalServiceKit/TSThread.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface SelectThreadViewController () <OWSTableViewControllerDelegate, ThreadViewHelperDelegate, UISearchBarDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
|
||||||
|
@property (nonatomic) NSSet<NSString *> *blockedPhoneNumberSet;
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
|
||||||
|
@property (nonatomic) NSArray<Contact *> *contacts;
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) ThreadViewHelper *threadViewHelper;
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
|
||||||
|
@property (nonatomic, readonly) UISearchBar *searchBar;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
@implementation SelectThreadViewController
|
||||||
|
|
||||||
|
- (void)loadView
|
||||||
|
{
|
||||||
|
[super loadView];
|
||||||
|
|
||||||
|
self.navigationItem.leftBarButtonItem =
|
||||||
|
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
|
||||||
|
target:self
|
||||||
|
action:@selector(dismissPressed:)];
|
||||||
|
|
||||||
|
self.view.backgroundColor = [UIColor whiteColor];
|
||||||
|
|
||||||
|
_blockingManager = [OWSBlockingManager sharedManager];
|
||||||
|
_blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]];
|
||||||
|
_contactsManager = [Environment getCurrent].contactsManager;
|
||||||
|
self.contacts = [self filteredContacts];
|
||||||
|
_threadViewHelper = [ThreadViewHelper new];
|
||||||
|
_threadViewHelper.delegate = self;
|
||||||
|
|
||||||
|
[self createViews];
|
||||||
|
|
||||||
|
[self addNotificationListeners];
|
||||||
|
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad
|
||||||
|
{
|
||||||
|
[super viewDidLoad];
|
||||||
|
[self.navigationController.navigationBar setTranslucent:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)addNotificationListeners
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(blockedPhoneNumbersDidChange:)
|
||||||
|
name:kNSNotificationName_BlockedPhoneNumbersDidChange
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(signalRecipientsDidChange:)
|
||||||
|
name:OWSContactsManagerSignalRecipientsDidChangeNotification
|
||||||
|
object:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)createViews
|
||||||
|
{
|
||||||
|
// Table
|
||||||
|
_tableViewController = [OWSTableViewController new];
|
||||||
|
_tableViewController.delegate = self;
|
||||||
|
_tableViewController.contents = [OWSTableContents new];
|
||||||
|
[self.view addSubview:self.tableViewController.view];
|
||||||
|
[_tableViewController.view autoPinWidthToSuperview];
|
||||||
|
[_tableViewController.view autoPinToTopLayoutGuideOfViewController:self withInset:0];
|
||||||
|
[_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||||
|
|
||||||
|
// Search
|
||||||
|
UISearchBar *searchBar = [UISearchBar new];
|
||||||
|
_searchBar = searchBar;
|
||||||
|
searchBar.searchBarStyle = UISearchBarStyleProminent;
|
||||||
|
searchBar.delegate = self;
|
||||||
|
searchBar.placeholder = NSLocalizedString(@"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT", @"");
|
||||||
|
[searchBar sizeToFit];
|
||||||
|
_tableViewController.tableView.tableHeaderView = searchBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UISearchBarDelegate
|
||||||
|
|
||||||
|
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||||
|
{
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
||||||
|
{
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
||||||
|
{
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar
|
||||||
|
{
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
|
||||||
|
{
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
- (void)updateTableContents
|
||||||
|
{
|
||||||
|
__weak SelectThreadViewController *weakSelf = self;
|
||||||
|
OWSTableContents *contents = [OWSTableContents new];
|
||||||
|
OWSTableSection *section = [OWSTableSection new];
|
||||||
|
|
||||||
|
// Threads
|
||||||
|
for (TSThread *thread in [self filteredThreadsWithSearchText]) {
|
||||||
|
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||||
|
SelectThreadViewController *strongSelf = weakSelf;
|
||||||
|
if (!strongSelf) {
|
||||||
|
return (InboxTableViewCell *)nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
InboxTableViewCell *cell = [InboxTableViewCell inboxTableViewCell];
|
||||||
|
|
||||||
|
[cell configureWithThread:thread
|
||||||
|
contactsManager:strongSelf.contactsManager
|
||||||
|
blockedPhoneNumberSet:strongSelf.blockedPhoneNumberSet];
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
customRowHeight:[InboxTableViewCell rowHeight]
|
||||||
|
actionBlock:^{
|
||||||
|
[weakSelf.delegate threadWasSelected:thread];
|
||||||
|
}]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contacts
|
||||||
|
NSArray<Contact *> *filteredContacts = [self filteredContactsWithSearchText];
|
||||||
|
for (Contact *contact in filteredContacts) {
|
||||||
|
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||||
|
SelectThreadViewController *strongSelf = weakSelf;
|
||||||
|
if (!strongSelf) {
|
||||||
|
return (InboxTableViewCell *)nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To be consistent with the threads (above), we use InboxTableViewCell
|
||||||
|
// instead of ContactTableViewCell to present contacts.
|
||||||
|
InboxTableViewCell *cell = [InboxTableViewCell inboxTableViewCell];
|
||||||
|
|
||||||
|
// TODO: Use ContactAccount.
|
||||||
|
NSString *recipientId = contact.textSecureIdentifiers.firstObject;
|
||||||
|
BOOL isBlocked = [strongSelf isContactBlocked:contact];
|
||||||
|
|
||||||
|
[cell configureWithContact:contact
|
||||||
|
recipientId:recipientId
|
||||||
|
contactsManager:strongSelf.contactsManager
|
||||||
|
isBlocked:isBlocked];
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
customRowHeight:[InboxTableViewCell rowHeight]
|
||||||
|
actionBlock:^{
|
||||||
|
[weakSelf contactWasSelected:contact];
|
||||||
|
}]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.itemCount < 1) {
|
||||||
|
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||||
|
UITableViewCell *cell = [UITableViewCell new];
|
||||||
|
cell.textLabel.text = NSLocalizedString(
|
||||||
|
@"SETTINGS_BLOCK_LIST_NO_CONTACTS", @"A label that indicates the user has no Signal contacts.");
|
||||||
|
cell.textLabel.font = [UIFont ows_regularFontWithSize:15.f];
|
||||||
|
cell.textLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.f];
|
||||||
|
cell.textLabel.textAlignment = NSTextAlignmentCenter;
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
actionBlock:nil]];
|
||||||
|
}
|
||||||
|
[contents addSection:section];
|
||||||
|
|
||||||
|
self.tableViewController.contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)contactWasSelected:(Contact *)contact
|
||||||
|
{
|
||||||
|
OWSAssert(contact);
|
||||||
|
OWSAssert(self.delegate);
|
||||||
|
|
||||||
|
// TODO: Use ContactAccount.
|
||||||
|
NSString *recipientId = contact.textSecureIdentifiers.firstObject;
|
||||||
|
|
||||||
|
if ([self isRecipientIdBlocked:recipientId] &&
|
||||||
|
![self.delegate canSelectBlockedContact]) {
|
||||||
|
|
||||||
|
__weak SelectThreadViewController *weakSelf = self;
|
||||||
|
[BlockListUIUtils showUnblockContactActionSheet:contact
|
||||||
|
fromViewController:self
|
||||||
|
blockingManager:self.blockingManager
|
||||||
|
contactsManager:self.contactsManager
|
||||||
|
completionBlock:^(BOOL isBlocked) {
|
||||||
|
if (!isBlocked) {
|
||||||
|
[weakSelf contactWasSelected:contact];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__block TSThread *thread = nil;
|
||||||
|
[[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||||
|
thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction];
|
||||||
|
}];
|
||||||
|
OWSAssert(thread);
|
||||||
|
|
||||||
|
[self.delegate threadWasSelected:thread];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Filter
|
||||||
|
|
||||||
|
- (NSArray<TSThread *> *)filteredThreadsWithSearchText
|
||||||
|
{
|
||||||
|
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
|
||||||
|
|
||||||
|
NSString *searchTerm =
|
||||||
|
[[self.searchBar text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move this to contacts view helper.
|
||||||
|
- (NSArray<Contact *> *)filteredContactsWithSearchText
|
||||||
|
{
|
||||||
|
// We don't want to show a 1:1 thread with Alice and Alice's contact,
|
||||||
|
// so we de-duplicate by recipientId.
|
||||||
|
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
|
||||||
|
NSMutableSet *contactIdsToIgnore = [NSMutableSet new];
|
||||||
|
for (TSThread *thread in threads) {
|
||||||
|
if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||||
|
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||||
|
[contactIdsToIgnore addObject:contactThread.contactIdentifier];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *searchString = [self.searchBar text];
|
||||||
|
|
||||||
|
NSArray *nonRedundantContacts =
|
||||||
|
[self.contacts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Contact *contact,
|
||||||
|
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||||
|
return ![contactIdsToIgnore containsObject:contact.textSecureIdentifiers.firstObject];
|
||||||
|
}]];
|
||||||
|
|
||||||
|
// TODO: Move this to contacts view helper.
|
||||||
|
OWSContactsSearcher *contactsSearcher = [[OWSContactsSearcher alloc] initWithContacts:nonRedundantContacts];
|
||||||
|
NSArray<Contact *> *filteredContacts = [contactsSearcher filterWithString:searchString];
|
||||||
|
|
||||||
|
return filteredContacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Contacts and Blocking
|
||||||
|
|
||||||
|
- (void)blockedPhoneNumbersDidChange:(id)notification
|
||||||
|
{
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
_blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]];
|
||||||
|
|
||||||
|
[self updateContacts];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)signalRecipientsDidChange:(NSNotification *)notification
|
||||||
|
{
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self updateContacts];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateContacts
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
|
||||||
|
self.contacts = [self filteredContacts];
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isContactBlocked:(Contact *)contact
|
||||||
|
{
|
||||||
|
if (contact.parsedPhoneNumbers.count < 1) {
|
||||||
|
// Hide contacts without any valid phone numbers.
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
|
||||||
|
if ([_blockedPhoneNumberSet containsObject:phoneNumber.toE164]) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isRecipientIdBlocked:(NSString *)recipientId
|
||||||
|
{
|
||||||
|
OWSAssert(recipientId.length > 0);
|
||||||
|
|
||||||
|
return [_blockedPhoneNumberSet containsObject:recipientId];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isContactHidden:(Contact *)contact
|
||||||
|
{
|
||||||
|
if (contact.parsedPhoneNumbers.count < 1) {
|
||||||
|
// Hide contacts without any valid phone numbers.
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self isCurrentUserContact:contact]) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isCurrentUserContact:(Contact *)contact
|
||||||
|
{
|
||||||
|
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
|
||||||
|
if ([[phoneNumber toE164] isEqualToString:[TSAccountManager localNumber]]) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray<Contact *> *_Nonnull)filteredContacts
|
||||||
|
{
|
||||||
|
NSMutableArray<Contact *> *result = [NSMutableArray new];
|
||||||
|
for (Contact *contact in self.contactsManager.signalContacts) {
|
||||||
|
if (![self isContactHidden:contact]) {
|
||||||
|
[result addObject:contact];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [result copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Events
|
||||||
|
|
||||||
|
- (void)dismissPressed:(id)sender
|
||||||
|
{
|
||||||
|
[self.searchBar resignFirstResponder];
|
||||||
|
[self dismissViewControllerAnimated:YES completion:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - OWSTableViewControllerDelegate
|
||||||
|
|
||||||
|
- (void)tableViewDidScroll
|
||||||
|
{
|
||||||
|
[self.searchBar resignFirstResponder];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - ThreadViewHelperDelegate
|
||||||
|
|
||||||
|
- (void)threadListDidChange
|
||||||
|
{
|
||||||
|
[self updateTableContents];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Logging
|
||||||
|
|
||||||
|
+ (NSString *)tag
|
||||||
|
{
|
||||||
|
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)tag
|
||||||
|
{
|
||||||
|
return self.class.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SelectThreadViewController.h"
|
||||||
|
|
||||||
|
@class SignalAttachment;
|
||||||
|
|
||||||
|
@interface SendExternalFileViewController : SelectThreadViewController
|
||||||
|
|
||||||
|
@property (nonatomic) SignalAttachment *attachment;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SendExternalFileViewController.h"
|
||||||
|
#import "Environment.h"
|
||||||
|
#import "Signal-Swift.h"
|
||||||
|
#import "ThreadUtil.h"
|
||||||
|
#import <SignalServiceKit/OWSMessageSender.h>
|
||||||
|
#import <SignalServiceKit/TSThread.h>
|
||||||
|
|
||||||
|
@interface SendExternalFileViewController () <SelectThreadViewControllerDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SendExternalFileViewController
|
||||||
|
|
||||||
|
- (void)loadView
|
||||||
|
{
|
||||||
|
[super loadView];
|
||||||
|
|
||||||
|
self.delegate = self;
|
||||||
|
|
||||||
|
_messageSender = [Environment getCurrent].messageSender;
|
||||||
|
|
||||||
|
self.title = NSLocalizedString(@"SEND_EXTERNAL_FILE_VIEW_TITLE", @"Title for the 'send external file' view.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - SelectThreadViewControllerDelegate
|
||||||
|
|
||||||
|
- (void)threadWasSelected:(TSThread *)thread
|
||||||
|
{
|
||||||
|
OWSAssert(self.attachment);
|
||||||
|
OWSAssert(thread);
|
||||||
|
|
||||||
|
// We should have a valid filename.
|
||||||
|
OWSAssert(self.attachment.filename.length > 0);
|
||||||
|
NSString *fileExtension = [self.attachment.filename pathExtension].lowercaseString;
|
||||||
|
OWSAssert(fileExtension.length > 0);
|
||||||
|
NSSet<NSString *> *textExtensions = [NSSet setWithArray:@[
|
||||||
|
@"txt",
|
||||||
|
@"url",
|
||||||
|
]];
|
||||||
|
NSString *text = nil;
|
||||||
|
if ([textExtensions containsObject:fileExtension]) {
|
||||||
|
text = [[NSString alloc] initWithData:self.attachment.data encoding:NSUTF8StringEncoding];
|
||||||
|
OWSAssert(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
[ThreadUtil sendMessageWithText:text inThread:thread messageSender:self.messageSender];
|
||||||
|
} else {
|
||||||
|
[ThreadUtil sendMessageWithAttachment:self.attachment inThread:thread messageSender:self.messageSender];
|
||||||
|
}
|
||||||
|
|
||||||
|
[Environment messageThreadId:thread.uniqueId];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)canSelectBlockedContact
|
||||||
|
{
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -204,17 +204,16 @@ class SignalAttachment: NSObject {
|
||||||
// Returns the file extension for this attachment or nil if no file extension
|
// Returns the file extension for this attachment or nil if no file extension
|
||||||
// can be identified.
|
// can be identified.
|
||||||
var fileExtension: String? {
|
var fileExtension: String? {
|
||||||
if dataUTI == SignalAttachment.kOversizeTextAttachmentUTI ||
|
if dataUTI == SignalAttachment.kOversizeTextAttachmentUTI {
|
||||||
dataUTI == SignalAttachment.kUnknownTestAttachmentUTI {
|
return "txt"
|
||||||
assertionFailure()
|
}
|
||||||
|
if dataUTI == SignalAttachment.kUnknownTestAttachmentUTI {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
guard let fileExtension = MIMETypeUtil.fileExtension(forUTIType:dataUTI) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return fileExtension
|
||||||
guard let fileExtension = UTTypeCopyPreferredTagWithClass(dataUTI as CFString,
|
|
||||||
kUTTagClassFilenameExtension) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fileExtension.takeRetainedValue() as String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the set of UTIs that correspond to valid _input_ image formats
|
// Returns the set of UTIs that correspond to valid _input_ image formats
|
||||||
|
@ -250,6 +249,16 @@ class SignalAttachment: NSObject {
|
||||||
return MIMETypeUtil.supportedAudioUTITypes()
|
return MIMETypeUtil.supportedAudioUTITypes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class var textUTISet: Set<String> {
|
||||||
|
return [
|
||||||
|
kUTTypeText as String,
|
||||||
|
kUTTypePlainText as String,
|
||||||
|
kUTTypeUTF8PlainText as String,
|
||||||
|
kUTTypeUTF16PlainText as String,
|
||||||
|
kUTTypeURL as String,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
public var isImage: Bool {
|
public var isImage: Bool {
|
||||||
return SignalAttachment.outputImageUTISet.contains(dataUTI)
|
return SignalAttachment.outputImageUTISet.contains(dataUTI)
|
||||||
}
|
}
|
||||||
|
@ -266,10 +275,26 @@ class SignalAttachment: NSObject {
|
||||||
return SignalAttachment.audioUTISet.contains(dataUTI)
|
return SignalAttachment.audioUTISet.contains(dataUTI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var isText: Bool {
|
||||||
|
return SignalAttachment.textUTISet.contains(dataUTI)
|
||||||
|
}
|
||||||
|
|
||||||
public class func pasteboardHasPossibleAttachment() -> Bool {
|
public class func pasteboardHasPossibleAttachment() -> Bool {
|
||||||
return UIPasteboard.general.numberOfItems > 0
|
return UIPasteboard.general.numberOfItems > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class func pasteBoardHasText() -> Bool {
|
||||||
|
if UIPasteboard.general.numberOfItems < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let itemSet = IndexSet(integer:0)
|
||||||
|
guard let pasteboardUTITypes = UIPasteboard.general.types(forItemSet:itemSet) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let pasteboardUTISet = Set<String>(pasteboardUTITypes[0])
|
||||||
|
return pasteboardUTISet.intersection(textUTISet).count > 0
|
||||||
|
}
|
||||||
|
|
||||||
// Returns an attachment from the pasteboard, or nil if no attachment
|
// Returns an attachment from the pasteboard, or nil if no attachment
|
||||||
// can be found.
|
// can be found.
|
||||||
//
|
//
|
||||||
|
|
|
@ -20,4 +20,11 @@
|
||||||
- (NSNumber *)updateInboxCountLabel;
|
- (NSNumber *)updateInboxCountLabel;
|
||||||
- (void)composeNew;
|
- (void)composeNew;
|
||||||
|
|
||||||
|
- (void)presentTopLevelModalViewController:(UIViewController *)viewController
|
||||||
|
animateDismissal:(BOOL)animateDismissal
|
||||||
|
animatePresentation:(BOOL)animatePresentation;
|
||||||
|
- (void)pushTopLevelViewController:(UIViewController *)viewController
|
||||||
|
animateDismissal:(BOOL)animateDismissal
|
||||||
|
animatePresentation:(BOOL)animatePresentation;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -528,7 +528,6 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS
|
||||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)presentThread:(TSThread *)thread
|
- (void)presentThread:(TSThread *)thread
|
||||||
keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing
|
keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing
|
||||||
callOnViewAppearing:(BOOL)callOnViewAppearing
|
callOnViewAppearing:(BOOL)callOnViewAppearing
|
||||||
|
@ -549,6 +548,67 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)presentTopLevelModalViewController:(UIViewController *)viewController
|
||||||
|
animateDismissal:(BOOL)animateDismissal
|
||||||
|
animatePresentation:(BOOL)animatePresentation
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
OWSAssert(viewController);
|
||||||
|
|
||||||
|
[self presentViewControllerWithBlock:^{
|
||||||
|
[self presentViewController:viewController animated:animatePresentation completion:nil];
|
||||||
|
}
|
||||||
|
animateDismissal:animateDismissal];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pushTopLevelViewController:(UIViewController *)viewController
|
||||||
|
animateDismissal:(BOOL)animateDismissal
|
||||||
|
animatePresentation:(BOOL)animatePresentation
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
OWSAssert(viewController);
|
||||||
|
|
||||||
|
[self presentViewControllerWithBlock:^{
|
||||||
|
[self.navigationController pushViewController:viewController animated:animatePresentation];
|
||||||
|
}
|
||||||
|
animateDismissal:animateDismissal];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)presentViewControllerWithBlock:(void (^)())presentationBlock animateDismissal:(BOOL)animateDismissal
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
OWSAssert(presentationBlock);
|
||||||
|
|
||||||
|
// Presenting a "top level" view controller has three steps:
|
||||||
|
//
|
||||||
|
// First, dismiss any presented modal.
|
||||||
|
// Second, pop to the root view controller if necessary.
|
||||||
|
// Third present the new view controller using presentationBlock.
|
||||||
|
|
||||||
|
// Define a block to perform the second step.
|
||||||
|
void (^dismissNavigationBlock)() = ^{
|
||||||
|
if (self.navigationController.viewControllers.lastObject != self) {
|
||||||
|
[CATransaction begin];
|
||||||
|
[CATransaction setCompletionBlock:^{
|
||||||
|
presentationBlock();
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self.navigationController popToViewController:self animated:animateDismissal];
|
||||||
|
|
||||||
|
[CATransaction commit];
|
||||||
|
} else {
|
||||||
|
presentationBlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform the first step.
|
||||||
|
if (self.presentedViewController) {
|
||||||
|
[self.presentedViewController dismissViewControllerAnimated:animateDismissal completion:dismissNavigationBlock];
|
||||||
|
} else {
|
||||||
|
dismissNavigationBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Navigation
|
#pragma mark - Navigation
|
||||||
|
|
||||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
@protocol ThreadViewHelperDelegate <NSObject>
|
||||||
|
|
||||||
|
- (void)threadListDidChange;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
@class TSThread;
|
||||||
|
|
||||||
|
// A helper class
|
||||||
|
@interface ThreadViewHelper : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id<ThreadViewHelperDelegate> delegate;
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) NSMutableArray<TSThread *> *threads;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,136 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ThreadViewHelper.h"
|
||||||
|
#import <SignalServiceKit/TSDatabaseView.h>
|
||||||
|
#import <SignalServiceKit/TSStorageManager.h>
|
||||||
|
#import <SignalServiceKit/TSThread.h>
|
||||||
|
#import <YapDatabase/YapDatabaseConnection.h>
|
||||||
|
#import <YapDatabase/YapDatabaseViewChange.h>
|
||||||
|
#import <YapDatabase/YapDatabaseViewConnection.h>
|
||||||
|
|
||||||
|
@interface ThreadViewHelper ()
|
||||||
|
|
||||||
|
@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection;
|
||||||
|
@property (nonatomic) YapDatabaseViewMappings *threadMappings;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ThreadViewHelper
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (!self) {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(yapDatabaseModified:)
|
||||||
|
name:TSUIDatabaseConnectionDidUpdateNotification
|
||||||
|
object:nil];
|
||||||
|
[self initializeMapping];
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)initializeMapping
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
|
||||||
|
NSString *grouping = TSInboxGroup;
|
||||||
|
|
||||||
|
self.threadMappings =
|
||||||
|
[[YapDatabaseViewMappings alloc] initWithGroups:@[ grouping ] view:TSThreadDatabaseViewExtensionName];
|
||||||
|
[self.threadMappings setIsReversed:YES forGroup:grouping];
|
||||||
|
|
||||||
|
__weak ThreadViewHelper *weakSelf = self;
|
||||||
|
[self.uiDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||||
|
[self.threadMappings updateWithTransaction:transaction];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[weakSelf updateThreads];
|
||||||
|
[weakSelf.delegate threadListDidChange];
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Database
|
||||||
|
|
||||||
|
- (YapDatabaseConnection *)uiDatabaseConnection
|
||||||
|
{
|
||||||
|
NSAssert([NSThread isMainThread], @"Must access uiDatabaseConnection on main thread!");
|
||||||
|
if (!_uiDatabaseConnection) {
|
||||||
|
YapDatabase *database = TSStorageManager.sharedManager.database;
|
||||||
|
_uiDatabaseConnection = [database newConnection];
|
||||||
|
[_uiDatabaseConnection beginLongLivedReadTransaction];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(yapDatabaseModified:)
|
||||||
|
name:YapDatabaseModifiedNotification
|
||||||
|
object:database];
|
||||||
|
}
|
||||||
|
return _uiDatabaseConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)yapDatabaseModified:(NSNotification *)notification
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
|
||||||
|
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
|
||||||
|
NSArray *sectionChanges = nil;
|
||||||
|
NSArray *rowChanges = nil;
|
||||||
|
[[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] getSectionChanges:§ionChanges
|
||||||
|
rowChanges:&rowChanges
|
||||||
|
forNotifications:notifications
|
||||||
|
withMappings:self.threadMappings];
|
||||||
|
if (sectionChanges.count == 0 && rowChanges.count == 0) {
|
||||||
|
// Ignore irrelevant modifications.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self updateThreads];
|
||||||
|
|
||||||
|
[self.delegate threadListDidChange];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateThreads
|
||||||
|
{
|
||||||
|
OWSAssert([NSThread isMainThread]);
|
||||||
|
|
||||||
|
NSMutableArray<TSThread *> *threads = [NSMutableArray new];
|
||||||
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||||
|
NSUInteger numberOfSections = [self.threadMappings numberOfSections];
|
||||||
|
OWSAssert(numberOfSections == 1);
|
||||||
|
for (NSUInteger section = 0; section < numberOfSections; section++) {
|
||||||
|
NSUInteger numberOfItems = [self.threadMappings numberOfItemsInSection:section];
|
||||||
|
for (NSUInteger item = 0; item < numberOfItems; item++) {
|
||||||
|
TSThread *thread = [[transaction extension:TSThreadDatabaseViewExtensionName]
|
||||||
|
objectAtIndexPath:[NSIndexPath indexPathForItem:(NSInteger)item inSection:(NSInteger)section]
|
||||||
|
withMappings:self.threadMappings];
|
||||||
|
[threads addObject:thread];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
_threads = threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Logging
|
||||||
|
|
||||||
|
+ (NSString *)tag
|
||||||
|
{
|
||||||
|
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)tag
|
||||||
|
{
|
||||||
|
return self.class.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -1,9 +1,5 @@
|
||||||
//
|
//
|
||||||
// OWSContactsSearcher.m
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
// Signal
|
|
||||||
//
|
|
||||||
// Created by Michael Kirk on 6/27/16.
|
|
||||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "OWSContactsSearcher.h"
|
#import "OWSContactsSearcher.h"
|
||||||
|
@ -34,6 +30,7 @@
|
||||||
|
|
||||||
NSString *formattedNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
|
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];
|
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(fullName contains[c] %@) OR (ANY parsedPhoneNumbers.toE164 contains[c] %@)", searchTerm, formattedNumber];
|
||||||
|
|
||||||
return [self.contacts filteredArrayUsingPredicate:predicate];
|
return [self.contacts filteredArrayUsingPredicate:predicate];
|
||||||
|
|
|
@ -699,7 +699,7 @@
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"NEW_GROUP_DEFAULT_TITLE" = "New Group";
|
"NEW_GROUP_DEFAULT_TITLE" = "New Group";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* Placeholder text for group name field */
|
||||||
"NEW_GROUP_NAMEGROUP_REQUEST_DEFAULT" = "Name this group chat";
|
"NEW_GROUP_NAMEGROUP_REQUEST_DEFAULT" = "Name this group chat";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
|
@ -910,12 +910,21 @@
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"SECURE_SESSION_RESET" = "Secure session was reset.";
|
"SECURE_SESSION_RESET" = "Secure session was reset.";
|
||||||
|
|
||||||
|
/* A label for the 'select contact' mode vs. 'select thread' mode. */
|
||||||
|
"SELECT_CONTACT_MODE_LABEL" = "Contacts";
|
||||||
|
|
||||||
|
/* A label for the 'select thread' mode vs. 'select contact' mode. */
|
||||||
|
"SELECT_THREAD_MODE_LABEL" = "Conversations";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"SEND_AGAIN_BUTTON" = "Send Again";
|
"SEND_AGAIN_BUTTON" = "Send Again";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"SEND_BUTTON_TITLE" = "Send";
|
"SEND_BUTTON_TITLE" = "Send";
|
||||||
|
|
||||||
|
/* Title for the 'send external file' view. */
|
||||||
|
"SEND_EXTERNAL_FILE_VIEW_TITLE" = "Send File";
|
||||||
|
|
||||||
/* Alert body after invite failed */
|
/* Alert body after invite failed */
|
||||||
"SEND_INVITE_FAILURE" = "Sending invite failed, please try again later.";
|
"SEND_INVITE_FAILURE" = "Sending invite failed, please try again later.";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue