From 3c7574a908e4c4a071d32e47d1d862990fbd1746 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 24 Apr 2017 22:30:46 -0400 Subject: [PATCH 1/8] Register Signal to handle a wide variety of common document types. // FREEBIE --- Signal/Signal-Info.plist | 148 ++++++++++++++++++ Signal/src/AppDelegate.m | 32 ++++ .../AttachmentApprovalViewController.swift | 6 +- .../DebugUITableViewController.m | 8 +- .../ViewControllers/MessagesViewController.m | 11 +- .../ViewControllers/SignalAttachment.swift | 12 +- 6 files changed, 201 insertions(+), 16 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 7bf96e281..73a358d68 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -116,5 +116,153 @@ UIViewControllerBasedStatusBarAppearance + CFBundleDocumentTypes + + + CFBundleTypeName + Signal File + CFBundleTypeIconFiles + + Icon + Icon@2x + Icon@3x + + LSItemContentTypes + + public.item + public.content + public.composite-content + public.message + public.contact + public.archive + public.disk-image + public.data + public.directory + com.apple.resolvable + public.symlink + public.executable + com.apple.mount-point + com.apple.alias-file + com.apple.alias-record + com.apple.bookmark + public.url + public.file-url + public.text + public.plain-text + public.utf8-plain-text + public.utf16-external-plain-text + public.utf16-plain-text + public.delimited-values-text + public.comma-separated-values-text + public.tab-separated-values-text + public.utf8-tab-separated-values-text + public.rtf + public.html + public.xml + public.source-code + public.assembly-source + public.c-source + public.objective-c-source + public.swift-source + public.c-plus-plus-source + public.objective-c-plus-plus-source + public.c-header + public.c-plus-plus-header + com.sun.java-source + public.script + com.apple.applescript.text + com.apple.applescript.script + com.apple.applescript.script-bundle + com.netscape.javascript-source + public.shell-script + public.perl-script + public.python-script + public.ruby-script + public.php-script + public.json + com.apple.property-list + com.apple.xml-property-list + com.apple.binary-property-list + com.adobe.pdf + com.apple.rtfd + com.apple.flat-rtfd + com.apple.txn.text-multimedia-data + com.apple.webarchive + public.image + public.jpeg + public.jpeg-2000 + public.tiff + com.apple.pict + com.compuserve.gif + public.png + com.apple.quicktime-image + com.apple.icns + com.microsoft.bmp + com.microsoft.ico + public.camera-raw-image + public.svg-image + com.apple.live-photo + public.audiovisual-content + public.movie + public.video + public.audio + com.apple.quicktime-movie + public.mpeg + public.mpeg-2-video + public.mpeg-2-transport-stream + public.mp3 + public.mpeg-4 + public.mpeg-4-audio + com.apple.protected-mpeg-4-audio + com.apple.protected-mpeg-4-video + public.avi + public.aiff-audio + com.microsoft.waveform-audio + public.midi-audio + public.playlist + public.m3u-playlist + public.folder + public.volume + com.apple.package + com.apple.bundle + com.apple.plugin + com.apple.metadata-importer + com.apple.quicklook-generator + com.apple.xpc-service + com.apple.framework + com.apple.application + com.apple.application-bundle + com.apple.application-file + public.unix-executable + com.microsoft.windows-executable + com.sun.java-class + com.sun.java-archive + com.apple.systempreference.prefpane + org.gnu.gnu-zip-archive + public.bzip2-archive + public.zip-archive + public.spreadsheet + public.presentation + public.database + public.vcard + public.to-do-item + public.calendar-event + public.email-message + com.apple.internet-location + com.apple.ink.inktext + public.font + public.bookmark + public.3d-content + com.rsa.pkcs-12 + public.x509-certificate + org.idpf.epub-container + public.log + com.adobe.pdf + public.pdf + + LSHandlerRank + Alternate + + diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index dec7d3c3d..7bdb787ca 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -263,6 +263,38 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; } else { DDLogWarn(@"Application opened with an unknown URL action: %@", url.host); } + } else if ([url.scheme.lowercaseString isEqualToString:@"file"]) { + NSString *filename = url.lastPathComponent; + if ([filename stringByDeletingPathExtension].length < 1) { + DDLogError(@"Application opened with URL invalid filename: %@", url); + return NO; + } + NSString *fileExtension = [filename pathExtension]; + if (fileExtension.length < 1) { + DDLogError(@"Application opened with URL missing file extension: %@", url); + return NO; + } + NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:fileExtension]; + if (utiType.length < 1) { + DDLogError(@"Application opened with URL of unknown UTI type: %@", url); + return NO; + } + NSData *data = [NSData dataWithContentsOfURL:url]; + if (!data) { + DDLogError(@"Application opened with URL with unloadable content: %@", url); + return NO; + } + SignalAttachment *attachment = [SignalAttachment attachmentWithData:data dataUTI:utiType filename:filename]; + if (!attachment) { + DDLogError(@"Application opened with URL with invalid content: %@", url); + return NO; + } + if ([attachment hasError]) { + DDLogError(@"Application opened with URL with content error: %@ %@", url, [attachment errorName]); + return NO; + } + DDLogInfo(@"Application opened with URL: %@", url); + return YES; } else { DDLogWarn(@"Application opened with an unknown URL scheme: %@", url.scheme); } diff --git a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift index 91102edf7..450be391d 100644 --- a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift +++ b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift @@ -26,9 +26,9 @@ class AttachmentApprovalViewController: UIViewController, OWSAudioAttachmentPlay @available(*, unavailable, message:"use attachment: constructor instead.") required init?(coder aDecoder: NSCoder) { - self.attachment = SignalAttachment.genericAttachment(data: nil, - dataUTI: kUTTypeContent as String, - filename:nil) + self.attachment = SignalAttachment.attachment(data: nil, + dataUTI: kUTTypeContent as String, + filename:nil) super.init(coder: aDecoder) assertionFailure() } diff --git a/Signal/src/ViewControllers/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUITableViewController.m index f4e9aa56a..e1d217d57 100644 --- a/Signal/src/ViewControllers/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUITableViewController.m @@ -139,8 +139,10 @@ NS_ASSUME_NONNULL_BEGIN for (int i=0; i < 32; i++) { [message appendString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel sem. Fusce sed nisl a lorem gravida tincidunt. Suspendisse efficitur non quam ac sodales. Aenean ut velit maximus, posuere sem a, accumsan nunc. Donec ullamcorper turpis lorem. Quisque dignissim purus eu placerat ultricies. Proin at urna eget mi semper congue. Aenean non elementum ex. Praesent pharetra quam at sem vestibulum, vestibulum ornare dolor elementum. Vestibulum massa tortor, scelerisque sit amet pulvinar a, rhoncus vitae nisl. Sed mi nunc, tempus at varius in, malesuada vitae dui. Vivamus efficitur pulvinar erat vitae congue. Proin vehicula turpis non felis congue facilisis. Nullam aliquet dapibus ligula ac mollis. Etiam sit amet posuere lorem, in rhoncus nisi."]; } - - SignalAttachment *attachment = [SignalAttachment oversizeTextAttachmentWithText:message]; + + SignalAttachment *attachment = [SignalAttachment attachmentWithData:[message dataUsingEncoding:NSUTF8StringEncoding] + dataUTI:SignalAttachment.kOversizeTextAttachmentUTI + filename:nil]; [ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender]; @@ -163,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN uti:(NSString *)uti { OWSMessageSender *messageSender = [Environment getCurrent].messageSender; SignalAttachment *attachment = - [SignalAttachment genericAttachmentWithData:[self createRandomNSDataOfSize:256] dataUTI:uti filename:nil]; + [SignalAttachment attachmentWithData:[self createRandomNSDataOfSize:256] dataUTI:uti filename:nil]; [ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender]; diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 9853a64d5..82bca67c5 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -1168,7 +1168,10 @@ typedef enum : NSUInteger { // which are presented as normal text messages. const NSUInteger kOversizeTextMessageSizeThreshold = 16 * 1024; if ([text lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= kOversizeTextMessageSizeThreshold) { - SignalAttachment *attachment = [SignalAttachment oversizeTextAttachmentWithText:text]; + SignalAttachment *attachment = + [SignalAttachment attachmentWithData:[text dataUsingEncoding:NSUTF8StringEncoding] + dataUTI:SignalAttachment.kOversizeTextAttachmentUTI + filename:nil]; [ThreadUtil sendMessageWithAttachment:attachment inThread:self.thread messageSender:self.messageSender]; } else { [ThreadUtil sendMessageWithText:text inThread:self.thread messageSender:self.messageSender]; @@ -2423,7 +2426,7 @@ typedef enum : NSUInteger { OWSAssert([NSThread isMainThread]); SignalAttachment *attachment = - [SignalAttachment imageAttachmentWithData:imageData dataUTI:dataUTI filename:filename]; + [SignalAttachment attachmentWithData:imageData dataUTI:dataUTI filename:filename]; [self dismissViewControllerAnimated:YES completion:^{ OWSAssert([NSThread isMainThread]); @@ -2488,7 +2491,7 @@ typedef enum : NSUInteger { NSData *videoData = [NSData dataWithContentsOfURL:compressedVideoUrl]; dispatch_async(dispatch_get_main_queue(), ^{ SignalAttachment *attachment = - [SignalAttachment videoAttachmentWithData:videoData dataUTI:(NSString *)kUTTypeMPEG4 filename:filename]; + [SignalAttachment attachmentWithData:videoData dataUTI:(NSString *)kUTTypeMPEG4 filename:filename]; if (!attachment || [attachment hasError]) { DDLogWarn(@"%@ %s Invalid attachment: %@.", self.tag, @@ -2715,7 +2718,7 @@ typedef enum : NSUInteger { if (flag) { NSData *audioData = [NSData dataWithContentsOfURL:recorder.url]; SignalAttachment *attachment = - [SignalAttachment audioAttachmentWithData:audioData dataUTI:(NSString *)kUTTypeMPEG4Audio filename:nil]; + [SignalAttachment attachmentWithData:audioData dataUTI:(NSString *)kUTTypeMPEG4Audio filename:nil]; if (!attachment || [attachment hasError]) { DDLogWarn(@"%@ %s Invalid attachment: %@.", diff --git a/Signal/src/ViewControllers/SignalAttachment.swift b/Signal/src/ViewControllers/SignalAttachment.swift index 1483edecd..4727b92c3 100644 --- a/Signal/src/ViewControllers/SignalAttachment.swift +++ b/Signal/src/ViewControllers/SignalAttachment.swift @@ -353,7 +353,7 @@ class SignalAttachment: NSObject { // // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. - public class func imageAttachment(data imageData: Data?, dataUTI: String, filename: String?) -> SignalAttachment { + private class func imageAttachment(data imageData: Data?, dataUTI: String, filename: String?) -> SignalAttachment { assert(dataUTI.characters.count > 0) assert(imageData != nil) @@ -539,7 +539,7 @@ class SignalAttachment: NSObject { // // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. - public class func videoAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment { + private class func videoAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment { return newAttachment(data : data, dataUTI : dataUTI, validUTISet : videoUTISet, @@ -553,7 +553,7 @@ class SignalAttachment: NSObject { // // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. - public class func audioAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment { + private class func audioAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment { return newAttachment(data : data, dataUTI : dataUTI, validUTISet : audioUTISet, @@ -567,12 +567,12 @@ class SignalAttachment: NSObject { // // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. - public class func oversizeTextAttachment(text: String?) -> SignalAttachment { + private class func oversizeTextAttachment(text: String?) -> SignalAttachment { return newAttachment(data : text?.data(using: .utf8), dataUTI : kOversizeTextAttachmentUTI, validUTISet : nil, maxFileSize : kMaxFileSizeGeneric, - filename : nil) + filename : nil) } // MARK: Generic Attachments @@ -581,7 +581,7 @@ class SignalAttachment: NSObject { // // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. - public class func genericAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment { + private class func genericAttachment(data: Data?, dataUTI: String, filename: String?) -> SignalAttachment { return newAttachment(data : data, dataUTI : dataUTI, validUTISet : nil, From 6e36ce97a5dd656c406c07044b637b8ee42a0250 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 25 Apr 2017 12:56:37 -0400 Subject: [PATCH 2/8] Let users share imported files to a thread or contact of their choice. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 18 + Signal/Signal-Info.plist | 137 +----- Signal/src/AppDelegate.m | 17 + .../src/ViewControllers/InboxTableViewCell.h | 11 + .../src/ViewControllers/InboxTableViewCell.m | 60 ++- .../ViewControllers/MessagesViewController.m | 16 +- .../ViewControllers/OWSTableViewController.h | 12 + .../ViewControllers/OWSTableViewController.m | 49 +- .../SelectThreadViewController.h | 21 + .../SelectThreadViewController.m | 428 ++++++++++++++++++ .../SendExternalFileViewController.h | 13 + .../SendExternalFileViewController.m | 66 +++ .../ViewControllers/SignalAttachment.swift | 41 +- .../ViewControllers/SignalsViewController.h | 7 + .../ViewControllers/SignalsViewController.m | 62 ++- Signal/src/ViewControllers/ThreadViewHelper.h | 22 + Signal/src/ViewControllers/ThreadViewHelper.m | 136 ++++++ Signal/src/contact/OWSContactsSearcher.m | 7 +- .../translations/en.lproj/Localizable.strings | 11 +- 19 files changed, 955 insertions(+), 179 deletions(-) create mode 100644 Signal/src/ViewControllers/SelectThreadViewController.h create mode 100644 Signal/src/ViewControllers/SelectThreadViewController.m create mode 100644 Signal/src/ViewControllers/SendExternalFileViewController.h create mode 100644 Signal/src/ViewControllers/SendExternalFileViewController.m create mode 100644 Signal/src/ViewControllers/ThreadViewHelper.h create mode 100644 Signal/src/ViewControllers/ThreadViewHelper.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 1b858cb17..514ec6b99 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* 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 */; }; 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 */; }; @@ -344,6 +347,12 @@ /* Begin PBXFileReference section */ 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 = ""; }; + 3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SendExternalFileViewController.m; sourceTree = ""; }; + 3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectThreadViewController.h; sourceTree = ""; }; + 3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SelectThreadViewController.m; sourceTree = ""; }; + 3400C7971EAFB772008A8584 /* ThreadViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadViewHelper.h; sourceTree = ""; }; + 3400C7981EAFB772008A8584 /* ThreadViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadViewHelper.m; sourceTree = ""; }; 341BB7471DB727EE001E2975 /* JSQMediaItem+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMediaItem+OWS.h"; sourceTree = ""; }; 341BB7481DB727EE001E2975 /* JSQMediaItem+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMediaItem+OWS.m"; sourceTree = ""; }; 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fontawesome-webfont.ttf"; sourceTree = ""; }; @@ -894,6 +903,10 @@ 34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */, 34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */, 34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */, + 3400C7941EAF99F4008A8584 /* SelectThreadViewController.h */, + 3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */, + 3400C7901EAF89CD008A8584 /* SendExternalFileViewController.h */, + 3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */, 34B3F8681E8DF1700035BE1A /* SettingsTableViewController.h */, 34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */, 34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */, @@ -903,6 +916,8 @@ 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */, 34B3F86F1E8DF1700035BE1A /* SignalsViewController.h */, 34B3F8701E8DF1700035BE1A /* SignalsViewController.m */, + 3400C7971EAFB772008A8584 /* ThreadViewHelper.h */, + 3400C7981EAFB772008A8584 /* ThreadViewHelper.m */, 34B3F8A01E8EA6040035BE1A /* ViewControllerUtils.h */, 34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */, ); @@ -1988,6 +2003,7 @@ B62D53F71A23CCAD009AAF82 /* TSMessageAdapter.m in Sources */, 76EB063C18170B33006006FC /* NumberUtil.m in Sources */, B6A3EB4B1A423B3800B2236B /* TSPhotoAdapter.m in Sources */, + 3400C7961EAF99F4008A8584 /* SelectThreadViewController.m in Sources */, 34B3F88F1E8DF1710035BE1A /* RegistrationViewController.m in Sources */, 34B3F8901E8DF1710035BE1A /* SettingsTableViewController.m in Sources */, 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */, @@ -2071,7 +2087,9 @@ 451DE9FD1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */, 45666F761D9BFE00008FE134 /* OWS100RemoveTSRecipientsMigration.m in Sources */, 34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */, + 3400C7931EAF89CD008A8584 /* SendExternalFileViewController.m in Sources */, FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */, + 3400C7991EAFB772008A8584 /* ThreadViewHelper.m in Sources */, 76EB054018170B33006006FC /* AppDelegate.m in Sources */, 341BB7491DB727EE001E2975 /* JSQMediaItem+OWS.m in Sources */, 34B3F89C1E8DF3270035BE1A /* BlockListViewController.m in Sources */, diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 73a358d68..c6c886585 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -119,146 +119,11 @@ CFBundleDocumentTypes - CFBundleTypeName - Signal File - CFBundleTypeIconFiles - - Icon - Icon@2x - Icon@3x - LSItemContentTypes - public.item - public.content - public.composite-content - public.message - public.contact public.archive - public.disk-image + public.content public.data - public.directory - com.apple.resolvable - public.symlink - public.executable - com.apple.mount-point - com.apple.alias-file - com.apple.alias-record - com.apple.bookmark - public.url - public.file-url - public.text - public.plain-text - public.utf8-plain-text - public.utf16-external-plain-text - public.utf16-plain-text - public.delimited-values-text - public.comma-separated-values-text - public.tab-separated-values-text - public.utf8-tab-separated-values-text - public.rtf - public.html - public.xml - public.source-code - public.assembly-source - public.c-source - public.objective-c-source - public.swift-source - public.c-plus-plus-source - public.objective-c-plus-plus-source - public.c-header - public.c-plus-plus-header - com.sun.java-source - public.script - com.apple.applescript.text - com.apple.applescript.script - com.apple.applescript.script-bundle - com.netscape.javascript-source - public.shell-script - public.perl-script - public.python-script - public.ruby-script - public.php-script - public.json - com.apple.property-list - com.apple.xml-property-list - com.apple.binary-property-list - com.adobe.pdf - com.apple.rtfd - com.apple.flat-rtfd - com.apple.txn.text-multimedia-data - com.apple.webarchive - public.image - public.jpeg - public.jpeg-2000 - public.tiff - com.apple.pict - com.compuserve.gif - public.png - com.apple.quicktime-image - com.apple.icns - com.microsoft.bmp - com.microsoft.ico - public.camera-raw-image - public.svg-image - com.apple.live-photo - public.audiovisual-content - public.movie - public.video - public.audio - com.apple.quicktime-movie - public.mpeg - public.mpeg-2-video - public.mpeg-2-transport-stream - public.mp3 - public.mpeg-4 - public.mpeg-4-audio - com.apple.protected-mpeg-4-audio - com.apple.protected-mpeg-4-video - public.avi - public.aiff-audio - com.microsoft.waveform-audio - public.midi-audio - public.playlist - public.m3u-playlist - public.folder - public.volume - com.apple.package - com.apple.bundle - com.apple.plugin - com.apple.metadata-importer - com.apple.quicklook-generator - com.apple.xpc-service - com.apple.framework - com.apple.application - com.apple.application-bundle - com.apple.application-file - public.unix-executable - com.microsoft.windows-executable - com.sun.java-class - com.sun.java-archive - com.apple.systempreference.prefpane - org.gnu.gnu-zip-archive - public.bzip2-archive - public.zip-archive - public.spreadsheet - public.presentation - public.database - public.vcard - public.to-do-item - public.calendar-event - public.email-message - com.apple.internet-location - com.apple.ink.inktext - public.font - public.bookmark - public.3d-content - com.rsa.pkcs-12 - public.x509-certificate - org.idpf.epub-container - public.log - com.adobe.pdf - public.pdf LSHandlerRank Alternate diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 7bdb787ca..cc8ccc7e6 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -15,6 +15,7 @@ #import "PropertyListPreferences.h" #import "PushManager.h" #import "Release.h" +#import "SendExternalFileViewController.h" #import "Signal-Swift.h" #import "TSMessagesManager.h" #import "TSSocketManager.h" @@ -294,6 +295,22 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; return NO; } 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; } else { DDLogWarn(@"Application opened with an unknown URL scheme: %@", url.scheme); diff --git a/Signal/src/ViewControllers/InboxTableViewCell.h b/Signal/src/ViewControllers/InboxTableViewCell.h index 3aa7282f1..155632b1e 100644 --- a/Signal/src/ViewControllers/InboxTableViewCell.h +++ b/Signal/src/ViewControllers/InboxTableViewCell.h @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN +@class Contact; @class OWSContactsManager; 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 *messageCounter; @property (nonatomic) NSString *threadId; +@property (nonatomic) NSString *contactId; + (instancetype)inboxTableViewCell; ++ (CGFloat)rowHeight; + - (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager blockedPhoneNumberSet:(NSSet *)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; @end diff --git a/Signal/src/ViewControllers/InboxTableViewCell.m b/Signal/src/ViewControllers/InboxTableViewCell.m index 89a595259..13ec2f167 100644 --- a/Signal/src/ViewControllers/InboxTableViewCell.m +++ b/Signal/src/ViewControllers/InboxTableViewCell.m @@ -5,6 +5,7 @@ #import "InboxTableViewCell.h" #import "Environment.h" #import "OWSAvatarBuilder.h" +#import "OWSContactAvatarBuilder.h" #import "PropertyListPreferences.h" #import "Signal-Swift.h" #import "TSContactThread.h" @@ -25,12 +26,14 @@ NS_ASSUME_NONNULL_BEGIN @interface InboxTableViewCell () -@property NSUInteger unreadMessages; -@property UIView *messagesBadge; -@property UILabel *unreadLabel; +@property (nonatomic) NSUInteger unreadMessages; +@property (nonatomic) UIView *messagesBadge; +@property (nonatomic) UILabel *unreadLabel; @end +#pragma mark - + @implementation InboxTableViewCell + (instancetype)inboxTableViewCell { @@ -41,6 +44,11 @@ NS_ASSUME_NONNULL_BEGIN return cell; } ++ (CGFloat)rowHeight +{ + return 72.f; +} + - (void)initializeLayout { 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 { _nameLabel.font = [UIFont ows_boldFontWithSize:14.0f]; _nameLabel.textColor = [UIColor ows_blackColor]; diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 82bca67c5..4a8571102 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -113,24 +113,10 @@ typedef enum : NSUInteger { return YES; } -- (BOOL)pasteBoardHasText -{ - if ([UIPasteboard generalPasteboard].numberOfItems < 1) { - return NO; - } - NSIndexSet *itemSet = [NSIndexSet indexSetWithIndex:0]; - NSSet *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 { // We don't want to load/convert images more than once so we // 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 { diff --git a/Signal/src/ViewControllers/OWSTableViewController.h b/Signal/src/ViewControllers/OWSTableViewController.h index f1129e76e..52d406733 100644 --- a/Signal/src/ViewControllers/OWSTableViewController.h +++ b/Signal/src/ViewControllers/OWSTableViewController.h @@ -35,6 +35,8 @@ extern const CGFloat kOWSTable_DefaultCellHeight; - (void)addItem:(OWSTableItem *)item; +- (NSUInteger)itemCount; + @end #pragma mark - @@ -69,8 +71,18 @@ typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(); #pragma mark - +@protocol OWSTableViewControllerDelegate + +- (void)tableViewDidScroll; + +@end + +#pragma mark - + @interface OWSTableViewController : UIViewController +@property (nonatomic, weak) id delegate; + @property (nonatomic) OWSTableContents *contents; @property (nonatomic, readonly) UITableView *tableView; diff --git a/Signal/src/ViewControllers/OWSTableViewController.m b/Signal/src/ViewControllers/OWSTableViewController.m index db89bbc4c..34907f12d 100644 --- a/Signal/src/ViewControllers/OWSTableViewController.m +++ b/Signal/src/ViewControllers/OWSTableViewController.m @@ -71,6 +71,11 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; [_items addObject:item]; } +- (NSUInteger)itemCount +{ + return _items.count; +} + @end #pragma mark - @@ -171,19 +176,15 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier"; @implementation OWSTableViewController -- (void)viewDidLoad -{ - [super viewDidLoad]; - [self.navigationController.navigationBar setTranslucent:NO]; -} - - (void)loadView { [super loadView]; 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.delegate = self; @@ -196,6 +197,20 @@ NSString * const kOWSTableCellIdentifier = @"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 { OWSAssert(self.contents); @@ -217,20 +232,27 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier"; return item; } +- (void)setContents:(OWSTableContents *)contents +{ + OWSAssert(contents); + + _contents = contents; + + [self.tableView reloadData]; +} + #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { OWSAssert(self.contents); - - OWSAssert(self.contents.sections.count > 0); return (NSInteger) self.contents.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex { OWSTableSection *section = [self sectionForIndex:sectionIndex]; - OWSAssert(section.items.count > 0); + OWSAssert(section.items); return (NSInteger) section.items.count; } @@ -356,6 +378,13 @@ NSString * const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier"; [self dismissViewControllerAnimated:YES completion:nil]; } +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + [self.delegate tableViewDidScroll]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/SelectThreadViewController.h b/Signal/src/ViewControllers/SelectThreadViewController.h new file mode 100644 index 000000000..dc812826e --- /dev/null +++ b/Signal/src/ViewControllers/SelectThreadViewController.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +@class TSThread; + +@protocol SelectThreadViewControllerDelegate + +- (void)threadWasSelected:(TSThread *)thread; + +- (BOOL)canSelectBlockedContact; + +@end + +#pragma mark - + +@interface SelectThreadViewController : UIViewController + +@property (nonatomic, weak) id delegate; + +@end diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m new file mode 100644 index 000000000..8ad82ff6a --- /dev/null +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -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 +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SelectThreadViewController () + +@property (nonatomic, readonly) OWSBlockingManager *blockingManager; +@property (nonatomic) NSSet *blockedPhoneNumberSet; + +@property (nonatomic, readonly) OWSContactsManager *contactsManager; +@property (nonatomic) NSArray *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 *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 *)filteredThreadsWithSearchText +{ + NSArray *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 *)filteredContactsWithSearchText +{ + // We don't want to show a 1:1 thread with Alice and Alice's contact, + // so we de-duplicate by recipientId. + NSArray *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 *_Nullable bindings) { + return ![contactIdsToIgnore containsObject:contact.textSecureIdentifiers.firstObject]; + }]]; + + // TODO: Move this to contacts view helper. + OWSContactsSearcher *contactsSearcher = [[OWSContactsSearcher alloc] initWithContacts:nonRedundantContacts]; + NSArray *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 *_Nonnull)filteredContacts +{ + NSMutableArray *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 diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.h b/Signal/src/ViewControllers/SendExternalFileViewController.h new file mode 100644 index 000000000..b0c04f44d --- /dev/null +++ b/Signal/src/ViewControllers/SendExternalFileViewController.h @@ -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 diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.m b/Signal/src/ViewControllers/SendExternalFileViewController.m new file mode 100644 index 000000000..19ad05022 --- /dev/null +++ b/Signal/src/ViewControllers/SendExternalFileViewController.m @@ -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 +#import + +@interface SendExternalFileViewController () + +@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 *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 diff --git a/Signal/src/ViewControllers/SignalAttachment.swift b/Signal/src/ViewControllers/SignalAttachment.swift index 4727b92c3..c5462596e 100644 --- a/Signal/src/ViewControllers/SignalAttachment.swift +++ b/Signal/src/ViewControllers/SignalAttachment.swift @@ -204,17 +204,16 @@ class SignalAttachment: NSObject { // Returns the file extension for this attachment or nil if no file extension // can be identified. var fileExtension: String? { - if dataUTI == SignalAttachment.kOversizeTextAttachmentUTI || - dataUTI == SignalAttachment.kUnknownTestAttachmentUTI { - assertionFailure() - return nil + if dataUTI == SignalAttachment.kOversizeTextAttachmentUTI { + return "txt" } - - guard let fileExtension = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, - kUTTagClassFilenameExtension) else { + if dataUTI == SignalAttachment.kUnknownTestAttachmentUTI { + return "unknown" + } + guard let fileExtension = MIMETypeUtil.fileExtension(forUTIType:dataUTI) else { return nil } - return fileExtension.takeRetainedValue() as String + return fileExtension } // Returns the set of UTIs that correspond to valid _input_ image formats @@ -250,6 +249,16 @@ class SignalAttachment: NSObject { return MIMETypeUtil.supportedAudioUTITypes() } + public class var textUTISet: Set { + return [ + kUTTypeText as String, + kUTTypePlainText as String, + kUTTypeUTF8PlainText as String, + kUTTypeUTF16PlainText as String, + kUTTypeURL as String, + ] + } + public var isImage: Bool { return SignalAttachment.outputImageUTISet.contains(dataUTI) } @@ -266,10 +275,26 @@ class SignalAttachment: NSObject { return SignalAttachment.audioUTISet.contains(dataUTI) } + public var isText: Bool { + return SignalAttachment.textUTISet.contains(dataUTI) + } + public class func pasteboardHasPossibleAttachment() -> Bool { 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(pasteboardUTITypes[0]) + return pasteboardUTISet.intersection(textUTISet).count > 0 + } + // Returns an attachment from the pasteboard, or nil if no attachment // can be found. // diff --git a/Signal/src/ViewControllers/SignalsViewController.h b/Signal/src/ViewControllers/SignalsViewController.h index b24c0dfd7..314fe30ee 100644 --- a/Signal/src/ViewControllers/SignalsViewController.h +++ b/Signal/src/ViewControllers/SignalsViewController.h @@ -20,4 +20,11 @@ - (NSNumber *)updateInboxCountLabel; - (void)composeNew; +- (void)presentTopLevelModalViewController:(UIViewController *)viewController + animateDismissal:(BOOL)animateDismissal + animatePresentation:(BOOL)animatePresentation; +- (void)pushTopLevelViewController:(UIViewController *)viewController + animateDismissal:(BOOL)animateDismissal + animatePresentation:(BOOL)animatePresentation; + @end diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m index d4bb955bd..6b07a2539 100644 --- a/Signal/src/ViewControllers/SignalsViewController.m +++ b/Signal/src/ViewControllers/SignalsViewController.m @@ -528,7 +528,6 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS [tableView deselectRowAtIndexPath:indexPath animated:YES]; } - - (void)presentThread:(TSThread *)thread keyboardOnViewAppearing:(BOOL)keyboardOnViewAppearing 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 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { diff --git a/Signal/src/ViewControllers/ThreadViewHelper.h b/Signal/src/ViewControllers/ThreadViewHelper.h new file mode 100644 index 000000000..86367e4ec --- /dev/null +++ b/Signal/src/ViewControllers/ThreadViewHelper.h @@ -0,0 +1,22 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +@protocol ThreadViewHelperDelegate + +- (void)threadListDidChange; + +@end + +#pragma mark - + +@class TSThread; + +// A helper class +@interface ThreadViewHelper : NSObject + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) NSMutableArray *threads; + +@end diff --git a/Signal/src/ViewControllers/ThreadViewHelper.m b/Signal/src/ViewControllers/ThreadViewHelper.m new file mode 100644 index 000000000..757aabd73 --- /dev/null +++ b/Signal/src/ViewControllers/ThreadViewHelper.m @@ -0,0 +1,136 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "ThreadViewHelper.h" +#import +#import +#import +#import +#import +#import + +@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 *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 diff --git a/Signal/src/contact/OWSContactsSearcher.m b/Signal/src/contact/OWSContactsSearcher.m index 9c44693fe..f776e444d 100644 --- a/Signal/src/contact/OWSContactsSearcher.m +++ b/Signal/src/contact/OWSContactsSearcher.m @@ -1,9 +1,5 @@ // -// OWSContactsSearcher.m -// Signal -// -// Created by Michael Kirk on 6/27/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "OWSContactsSearcher.h" @@ -34,6 +30,7 @@ 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]; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0cdff1500..247e7711e 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -699,7 +699,7 @@ /* No comment provided by engineer. */ "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"; /* No comment provided by engineer. */ @@ -910,12 +910,21 @@ /* No comment provided by engineer. */ "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. */ "SEND_AGAIN_BUTTON" = "Send Again"; /* No comment provided by engineer. */ "SEND_BUTTON_TITLE" = "Send"; +/* Title for the 'send external file' view. */ +"SEND_EXTERNAL_FILE_VIEW_TITLE" = "Send File"; + /* Alert body after invite failed */ "SEND_INVITE_FAILURE" = "Sending invite failed, please try again later."; From e75ed5e477ddab9098f1f89d699b07b3e4ab6067 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Apr 2017 12:25:41 -0400 Subject: [PATCH 3/8] Respond to CR. // FREEBIE --- .../TSMessageAdapaters/TSAnimatedAdapter.m | 4 +-- .../ViewControllers/MessagesViewController.m | 9 ++++--- .../SelectThreadViewController.h | 4 +++ .../SelectThreadViewController.m | 15 ----------- .../SendExternalFileViewController.h | 4 +++ .../SendExternalFileViewController.m | 26 +++++-------------- .../ViewControllers/SignalAttachment.swift | 2 +- Signal/src/ViewControllers/ThreadViewHelper.h | 6 ++++- Signal/src/ViewControllers/ThreadViewHelper.m | 2 +- 9 files changed, 29 insertions(+), 43 deletions(-) diff --git a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m index d1617ce51..109a18c29 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m @@ -131,8 +131,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)performEditingAction:(SEL)action { if (action == @selector(copy:)) { - UIPasteboard *pasteBoard = UIPasteboard.generalPasteboard; - [pasteBoard setData:self.fileData forPasteboardType:(__bridge NSString *)kUTTypeGIF]; + UIPasteboard *pasteboard = UIPasteboard.generalPasteboard; + [pasteboard setData:self.fileData forPasteboardType:(__bridge NSString *)kUTTypeGIF]; } else if (action == NSSelectorFromString(@"save:")) { NSData *photoData = self.fileData; ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; diff --git a/Signal/src/ViewControllers/MessagesViewController.m b/Signal/src/ViewControllers/MessagesViewController.m index 4a8571102..39dfe7f67 100644 --- a/Signal/src/ViewControllers/MessagesViewController.m +++ b/Signal/src/ViewControllers/MessagesViewController.m @@ -113,15 +113,16 @@ typedef enum : NSUInteger { return YES; } -- (BOOL)pasteBoardHasPossibleAttachment { +- (BOOL)pasteboardHasPossibleAttachment +{ // We don't want to load/convert images more than once so we // only do a cursory validation pass at this time. - return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteBoardHasText]); + return ([SignalAttachment pasteboardHasPossibleAttachment] && ![SignalAttachment pasteboardHasText]); } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if (action == @selector(paste:)) { - if ([self pasteBoardHasPossibleAttachment]) { + if ([self pasteboardHasPossibleAttachment]) { return YES; } } @@ -129,7 +130,7 @@ typedef enum : NSUInteger { } - (void)paste:(id)sender { - if ([self pasteBoardHasPossibleAttachment]) { + if ([self pasteboardHasPossibleAttachment]) { SignalAttachment *attachment = [SignalAttachment attachmentFromPasteboard]; // Note: attachment might be nil or have an error at this point; that's fine. [self.textViewPasteDelegate didPasteAttachment:attachment]; diff --git a/Signal/src/ViewControllers/SelectThreadViewController.h b/Signal/src/ViewControllers/SelectThreadViewController.h index dc812826e..089f377b2 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.h +++ b/Signal/src/ViewControllers/SelectThreadViewController.h @@ -4,6 +4,8 @@ @class TSThread; +NS_ASSUME_NONNULL_BEGIN + @protocol SelectThreadViewControllerDelegate - (void)threadWasSelected:(TSThread *)thread; @@ -19,3 +21,5 @@ @property (nonatomic, weak) id delegate; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index 8ad82ff6a..471b8f820 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -360,21 +360,6 @@ NS_ASSUME_NONNULL_BEGIN 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; } diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.h b/Signal/src/ViewControllers/SendExternalFileViewController.h index b0c04f44d..45f58c895 100644 --- a/Signal/src/ViewControllers/SendExternalFileViewController.h +++ b/Signal/src/ViewControllers/SendExternalFileViewController.h @@ -4,6 +4,8 @@ #import "SelectThreadViewController.h" +NS_ASSUME_NONNULL_BEGIN + @class SignalAttachment; @interface SendExternalFileViewController : SelectThreadViewController @@ -11,3 +13,5 @@ @property (nonatomic) SignalAttachment *attachment; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.m b/Signal/src/ViewControllers/SendExternalFileViewController.m index 19ad05022..f2cd236d8 100644 --- a/Signal/src/ViewControllers/SendExternalFileViewController.m +++ b/Signal/src/ViewControllers/SendExternalFileViewController.m @@ -9,12 +9,16 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @interface SendExternalFileViewController () @property (nonatomic, readonly) OWSMessageSender *messageSender; @end +#pragma mark - + @implementation SendExternalFileViewController - (void)loadView @@ -35,25 +39,7 @@ 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 *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]; - } + [ThreadUtil sendMessageWithAttachment:self.attachment inThread:thread messageSender:self.messageSender]; [Environment messageThreadId:thread.uniqueId]; } @@ -64,3 +50,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/SignalAttachment.swift b/Signal/src/ViewControllers/SignalAttachment.swift index c5462596e..584948a0b 100644 --- a/Signal/src/ViewControllers/SignalAttachment.swift +++ b/Signal/src/ViewControllers/SignalAttachment.swift @@ -283,7 +283,7 @@ class SignalAttachment: NSObject { return UIPasteboard.general.numberOfItems > 0 } - public class func pasteBoardHasText() -> Bool { + public class func pasteboardHasText() -> Bool { if UIPasteboard.general.numberOfItems < 1 { return false } diff --git a/Signal/src/ViewControllers/ThreadViewHelper.h b/Signal/src/ViewControllers/ThreadViewHelper.h index 86367e4ec..5d88fce1a 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.h +++ b/Signal/src/ViewControllers/ThreadViewHelper.h @@ -12,7 +12,11 @@ @class TSThread; -// A helper class +// A helper class for views that want to present the list of threads +// that show up in home view, and in the same order. +// +// It observes changes to the threads & their ordering and informs +// its delegate when they happen. @interface ThreadViewHelper : NSObject @property (nonatomic, weak) id delegate; diff --git a/Signal/src/ViewControllers/ThreadViewHelper.m b/Signal/src/ViewControllers/ThreadViewHelper.m index 757aabd73..cfb12b421 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.m +++ b/Signal/src/ViewControllers/ThreadViewHelper.m @@ -118,7 +118,7 @@ } }]; - _threads = threads; + _threads = [threads copy]; } #pragma mark - Logging From 5c0c9b533e46abd3275351ac3e2d524e794151eb Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Apr 2017 13:03:51 -0400 Subject: [PATCH 4/8] Respond to CR. // FREEBIE --- .../SelectThreadViewController.h | 2 + .../SelectThreadViewController.m | 46 +++++++++---------- .../SendExternalFileViewController.m | 5 ++ Signal/src/views/ContactTableViewCell.h | 3 ++ Signal/src/views/ContactTableViewCell.m | 26 ++++++++++- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/Signal/src/ViewControllers/SelectThreadViewController.h b/Signal/src/ViewControllers/SelectThreadViewController.h index 089f377b2..b09abf4c4 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.h +++ b/Signal/src/ViewControllers/SelectThreadViewController.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)canSelectBlockedContact; +- (nullable UIView *)createHeader:(UIView *)superview; + @end #pragma mark - diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index 471b8f820..656e492dd 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -90,13 +90,19 @@ NS_ASSUME_NONNULL_BEGIN - (void)createViews { + UIView *header = [self.delegate createHeader:self.view]; + // 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]; + if (header) { + [_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:header]; + } else { + [_tableViewController.view autoPinToTopLayoutGuideOfViewController:self withInset:0]; + } [_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeBottom]; // Search @@ -149,18 +155,16 @@ NS_ASSUME_NONNULL_BEGIN [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ SelectThreadViewController *strongSelf = weakSelf; if (!strongSelf) { - return (InboxTableViewCell *)nil; + return (ContactTableViewCell *)nil; } - InboxTableViewCell *cell = [InboxTableViewCell inboxTableViewCell]; - - [cell configureWithThread:thread - contactsManager:strongSelf.contactsManager - blockedPhoneNumberSet:strongSelf.blockedPhoneNumberSet]; - + // To be consistent with the threads (above), we use ContactTableViewCell + // instead of InboxTableViewCell to present contacts and threads. + ContactTableViewCell *cell = [ContactTableViewCell new]; + [cell configureWithThread:thread contactsManager:strongSelf.contactsManager]; return cell; } - customRowHeight:[InboxTableViewCell rowHeight] + customRowHeight:[ContactTableViewCell rowHeight] actionBlock:^{ [weakSelf.delegate threadWasSelected:thread]; }]]; @@ -172,25 +176,21 @@ NS_ASSUME_NONNULL_BEGIN [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ SelectThreadViewController *strongSelf = weakSelf; if (!strongSelf) { - return (InboxTableViewCell *)nil; + return (ContactTableViewCell *)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; + ContactTableViewCell *cell = [ContactTableViewCell new]; BOOL isBlocked = [strongSelf isContactBlocked:contact]; - - [cell configureWithContact:contact - recipientId:recipientId - contactsManager:strongSelf.contactsManager - isBlocked:isBlocked]; - + if (isBlocked) { + cell.accessoryMessage + = NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked."); + } else { + OWSAssert(cell.accessoryMessage == nil); + } + [cell configureWithContact:contact contactsManager:strongSelf.contactsManager]; return cell; } - customRowHeight:[InboxTableViewCell rowHeight] + customRowHeight:[ContactTableViewCell rowHeight] actionBlock:^{ [weakSelf contactWasSelected:contact]; }]]; diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.m b/Signal/src/ViewControllers/SendExternalFileViewController.m index f2cd236d8..e3a51e658 100644 --- a/Signal/src/ViewControllers/SendExternalFileViewController.m +++ b/Signal/src/ViewControllers/SendExternalFileViewController.m @@ -49,6 +49,11 @@ NS_ASSUME_NONNULL_BEGIN return NO; } +- (nullable UIView *)createHeader:(UIView *)superview +{ + return nil; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/ContactTableViewCell.h b/Signal/src/views/ContactTableViewCell.h index 25080871a..445e92d51 100644 --- a/Signal/src/views/ContactTableViewCell.h +++ b/Signal/src/views/ContactTableViewCell.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const kContactsTable_CellReuseIdentifier; @class OWSContactsManager; +@class TSThread; @interface ContactTableViewCell : UITableViewCell @@ -29,6 +30,8 @@ extern NSString *const kContactsTable_CellReuseIdentifier; - (void)configureWithRecipientId:(NSString *)recipientId contactsManager:(OWSContactsManager *)contactsManager; +- (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/ContactTableViewCell.m b/Signal/src/views/ContactTableViewCell.m index acddb70e8..82fd906ee 100644 --- a/Signal/src/views/ContactTableViewCell.m +++ b/Signal/src/views/ContactTableViewCell.m @@ -9,6 +9,8 @@ #import "UIFont+OWS.h" #import "UIUtil.h" #import "UIView+OWS.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -50,11 +52,11 @@ NSString *const kContactsTable_CellReuseIdentifier = @"kContactsTable_CellReuseI { const CGFloat kAvatarSize = 40.f; _avatarView = [UIImageView new]; - _avatarView.contentMode = UIViewContentModeScaleToFill; _avatarView.image = [UIImage imageNamed:@"empty-group-avatar"]; // applyRoundedBorderToImageView requires the avatar to have // the correct size. _avatarView.frame = CGRectMake(0, 0, kAvatarSize, kAvatarSize); + _avatarView.contentMode = UIViewContentModeScaleToFill; _avatarView.layer.minificationFilter = kCAFilterTrilinear; _avatarView.layer.magnificationFilter = kCAFilterTrilinear; [self.contentView addSubview:_avatarView]; @@ -121,6 +123,28 @@ NSString *const kContactsTable_CellReuseIdentifier = @"kContactsTable_CellReuseI [self layoutSubviews]; } +- (void)configureWithThread:(TSThread *)thread contactsManager:(OWSContactsManager *)contactsManager +{ + OWSAssert(thread); + + NSString *threadName = thread.name; + if (threadName.length == 0 && [thread isKindOfClass:[TSGroupThread class]]) { + threadName = NSLocalizedString(@"NEW_GROUP_DEFAULT_TITLE", @""); + } + + NSAttributedString *attributedText = [[NSAttributedString alloc] + initWithString:threadName + attributes:@{ + NSForegroundColorAttributeName : [UIColor blackColor], + }]; + self.nameLabel.attributedText = attributedText; + + self.avatarView.image = [OWSAvatarBuilder buildImageForThread:thread contactsManager:contactsManager]; + + // Force layout, since imageView isn't being initally rendered on App Store optimized build. + [self layoutSubviews]; +} + - (void)layoutSubviews { [super layoutSubviews]; From 93eed735308abb1b883fc251452d7beb5a0d656b Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Apr 2017 14:35:49 -0400 Subject: [PATCH 5/8] Respond to CR. // FREEBIE --- .../SelectThreadViewController.m | 2 + .../SendExternalFileViewController.m | 93 ++++++++++++++++++- .../ViewControllers/SignalsViewController.m | 14 +++ .../translations/en.lproj/Localizable.strings | 9 +- 4 files changed, 109 insertions(+), 9 deletions(-) diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index 656e492dd..0230aa8b3 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -90,6 +90,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)createViews { + OWSAssert(self.delegate); + UIView *header = [self.delegate createHeader:self.view]; // Table diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.m b/Signal/src/ViewControllers/SendExternalFileViewController.m index e3a51e658..bbcac0a93 100644 --- a/Signal/src/ViewControllers/SendExternalFileViewController.m +++ b/Signal/src/ViewControllers/SendExternalFileViewController.m @@ -6,6 +6,8 @@ #import "Environment.h" #import "Signal-Swift.h" #import "ThreadUtil.h" +#import "UIColor+OWS.h" +#import "UIFont+OWS.h" #import #import @@ -21,12 +23,18 @@ NS_ASSUME_NONNULL_BEGIN @implementation SendExternalFileViewController +- (instancetype)init +{ + if (self = [super init]) { + self.delegate = self; + } + return self; +} + - (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."); @@ -51,7 +59,86 @@ NS_ASSUME_NONNULL_BEGIN - (nullable UIView *)createHeader:(UIView *)superview { - return nil; + OWSAssert(superview) + + const CGFloat imageSize + = ScaleFromIPhone5To7Plus(40, 40); + const CGFloat imageLabelSpacing = ScaleFromIPhone5To7Plus(5, 5); + const CGFloat titleVSpacing = ScaleFromIPhone5To7Plus(10, 10); + const CGFloat contentVMargin = ScaleFromIPhone5To7Plus(20, 20); + + UIView *header = [UIView new]; + [superview addSubview:header]; + [header autoPinWidthToSuperview]; + [header autoPinToTopLayoutGuideOfViewController:self withInset:0]; + + UIView *titleLabel = [self createTitleLabel]; + [header addSubview:titleLabel]; + [titleLabel autoHCenterInSuperview]; + [titleLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:contentVMargin]; + + UIView *fileView = [UIView new]; + [header addSubview:fileView]; + [fileView autoHCenterInSuperview]; + [fileView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:contentVMargin]; + [fileView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:titleLabel withOffset:titleVSpacing]; + + UIImage *image = [UIImage imageNamed:@"file-thin-black-large"]; + OWSAssert(image); + UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; + imageView.layer.minificationFilter = kCAFilterTrilinear; + imageView.layer.magnificationFilter = kCAFilterTrilinear; + [fileView addSubview:imageView]; + [imageView autoSetDimension:ALDimensionWidth toSize:imageSize]; + [imageView autoSetDimension:ALDimensionHeight toSize:imageSize]; + [imageView autoPinEdgeToSuperviewEdge:ALEdgeLeft]; + [imageView autoPinEdgeToSuperviewEdge:ALEdgeTop]; + [imageView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; + + UIView *fileNameLabel = [self createFileNameLabel]; + [fileView addSubview:fileNameLabel]; + [fileNameLabel autoAlignAxis:ALAxisHorizontal toSameAxisOfView:imageView]; + [fileNameLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:imageView withOffset:imageLabelSpacing]; + [fileNameLabel autoPinEdgeToSuperviewEdge:ALEdgeRight]; + + return header; +} + +- (NSString *)formattedFileName +{ + OWSAssert(self.attachment) NSString *filename = + [self.attachment.filename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + OWSAssert(filename.length > 0); + const NSUInteger kMaxFilenameLength = 50; + if (filename.length > kMaxFilenameLength) { + // Truncate the filename if necessary. + // + // TODO: Use l10n-safe truncation. + filename = [[[filename substringToIndex:kMaxFilenameLength / 2] stringByAppendingString:@"…"] + stringByAppendingString:[filename substringFromIndex:filename.length - kMaxFilenameLength / 2]]; + } + return filename; +} + +- (UIView *)createFileNameLabel +{ + UILabel *label = [UILabel new]; + label.text = [self formattedFileName]; + label.textColor = [UIColor ows_materialBlueColor]; + label.font = [UIFont ows_regularFontWithSize:16.f]; + label.lineBreakMode = NSLineBreakByTruncatingMiddle; + return label; +} + + +- (UIView *)createTitleLabel +{ + UILabel *label = [UILabel new]; + label.text + = NSLocalizedString(@"SEND_EXTERNAL_FILE_HEADER_TITLE", @"Header title for the 'send external file' view."); + label.textColor = [UIColor blackColor]; + label.font = [UIFont ows_mediumFontWithSize:18.f]; + return label; } @end diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m index 6b07a2539..d4ab06d04 100644 --- a/Signal/src/ViewControllers/SignalsViewController.m +++ b/Signal/src/ViewControllers/SignalsViewController.m @@ -10,6 +10,7 @@ #import "OWSContactsManager.h" #import "PropertyListPreferences.h" #import "PushManager.h" +#import "SendExternalFileViewController.h" #import "Signal-Swift.h" #import "TSAccountManager.h" #import "TSDatabaseView.h" @@ -163,6 +164,19 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS object:nil]; [self updateBarButtonItems]; + + dispatch_async(dispatch_get_main_queue(), ^{ + SignalAttachment *attachment = + [SignalAttachment attachmentWithData:[@"hi" dataUsingEncoding:NSUTF8StringEncoding] + dataUTI:(NSString *)kUTTypeText + filename:@"boink.txt"]; + + SendExternalFileViewController *viewController = [SendExternalFileViewController new]; + viewController.attachment = attachment; + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:viewController]; + [self presentTopLevelModalViewController:navigationController animateDismissal:NO animatePresentation:YES]; + }); } - (void)updateBarButtonItems { diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 247e7711e..954f72407 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -910,18 +910,15 @@ /* No comment provided by engineer. */ "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. */ "SEND_AGAIN_BUTTON" = "Send Again"; /* No comment provided by engineer. */ "SEND_BUTTON_TITLE" = "Send"; +/* Header title for the 'send external file' view. */ +"SEND_EXTERNAL_FILE_HEADER_TITLE" = "Select a Recipient for:"; + /* Title for the 'send external file' view. */ "SEND_EXTERNAL_FILE_VIEW_TITLE" = "Send File"; From c84da982ae2df644ff0647aa1fe65a269a7d4604 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Apr 2017 14:41:01 -0400 Subject: [PATCH 6/8] Respond to CR. // FREEBIE --- Signal/src/ViewControllers/SignalsViewController.m | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m index d4ab06d04..6b07a2539 100644 --- a/Signal/src/ViewControllers/SignalsViewController.m +++ b/Signal/src/ViewControllers/SignalsViewController.m @@ -10,7 +10,6 @@ #import "OWSContactsManager.h" #import "PropertyListPreferences.h" #import "PushManager.h" -#import "SendExternalFileViewController.h" #import "Signal-Swift.h" #import "TSAccountManager.h" #import "TSDatabaseView.h" @@ -164,19 +163,6 @@ NSString *const SignalsViewControllerSegueShowIncomingCall = @"ShowIncomingCallS object:nil]; [self updateBarButtonItems]; - - dispatch_async(dispatch_get_main_queue(), ^{ - SignalAttachment *attachment = - [SignalAttachment attachmentWithData:[@"hi" dataUsingEncoding:NSUTF8StringEncoding] - dataUTI:(NSString *)kUTTypeText - filename:@"boink.txt"]; - - SendExternalFileViewController *viewController = [SendExternalFileViewController new]; - viewController.attachment = attachment; - UINavigationController *navigationController = - [[UINavigationController alloc] initWithRootViewController:viewController]; - [self presentTopLevelModalViewController:navigationController animateDismissal:NO animatePresentation:YES]; - }); } - (void)updateBarButtonItems { From d081df9ded10693200e49f41efe59e0c30e78205 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Apr 2017 15:44:21 -0400 Subject: [PATCH 7/8] Respond to CR. // FREEBIE --- .../Contents.json | 21 ++++++++++ .../file-thin-black-w-shadow-large.png | Bin 0 -> 37440 bytes .../AttachmentApprovalViewController.swift | 7 +++- .../SelectThreadViewController.h | 2 +- .../SelectThreadViewController.m | 25 ++++++------ .../SendExternalFileViewController.m | 37 ++++++++++++------ .../ViewControllers/SignalAttachment.swift | 7 ++++ .../translations/en.lproj/Localizable.strings | 3 ++ 8 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 Signal/Images.xcassets/file-thin-black-filled-large.imageset/Contents.json create mode 100644 Signal/Images.xcassets/file-thin-black-filled-large.imageset/file-thin-black-w-shadow-large.png diff --git a/Signal/Images.xcassets/file-thin-black-filled-large.imageset/Contents.json b/Signal/Images.xcassets/file-thin-black-filled-large.imageset/Contents.json new file mode 100644 index 000000000..00bbda011 --- /dev/null +++ b/Signal/Images.xcassets/file-thin-black-filled-large.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "file-thin-black-w-shadow-large.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/file-thin-black-filled-large.imageset/file-thin-black-w-shadow-large.png b/Signal/Images.xcassets/file-thin-black-filled-large.imageset/file-thin-black-w-shadow-large.png new file mode 100644 index 0000000000000000000000000000000000000000..cfb32922490c569ca926d1678efd93424ec5860c GIT binary patch literal 37440 zcmeHwc{J2({67lKkD zyU3EPS+o6~&u5tVOm%;M{LcA(&-tD+&bjAKGtcvR-uwIYe!rjLhK|;$9d!HXC@3g) zsGUB3j)HJf&0`c4Ig#`$7S!P1v~H&_dQec%GeiGTQY1cL z18ex~&l`9eXllq=x;hJ6Sh-%Z7WQ>^1FI=06ny2tOJ{3O3$(AZlZ%I(uObh;LJqu! z-WK6O!%I9J6?qIabwTGp zuEix+FHc1t9_S##51vj>dmG|OE*=|f0R}~&PejCoMMZwM&Dz)gKej=i{O2|+O9EGJ zUhYmXPga&9)=t*W)-IkNV4v7;j{~wLY$PGl*TM~d7%W6QZwe6Qa_6i)T%Eii5nOPw z_f!&BfEoJvZK6c*yHs7B-Q2A`Jis<3sf{hj_kLb)Ks-iCX7dc>n?G;RcDDzdT0nFu ziESK!eDCM=7k_sE@=e4BNc3_#_P*9m2FLAzKw&P##6?9FM1K3x&s$Ypom|~@-7GAv zA=2P2h_`-T4IgyzzYjuugxG3jDd*vJ$-&yv6QMDnH`xAFENn#(Z!3uWcC`Yq202Yv zD|?$O$1OaqmBdBGWaLC8<;11YVv^7cFZAno;SBu~ zEg>w8---NuW2=G)WXP;{d z{KSG^n7xJ)2{l?977fZ-%lAG7$8Q}cZ04n^_+&yj$JkSqvU$%~*?U_afCIca2X(-< zR=G<>d$5i)iwrLuQ+@xyuP%6md))u2>`ni6?Ojktz%nrFn0vKjJ6+rGAKD z(@O@0PCXL+8rFMb2b?;vUb}S6GnoJCWGc85f*O+(<>bQ)&_%$FTVc7_B4G0RD+M(; z$H*PWU|YDiIVU-Thv!E0l*2@&PN62RbKJf@T~R*yb}Q_GxP8=bZY=MkG|?jDrqmOt z{P1Pr@XJQGCMNUn8%0rcFUt4FrV^4Dhk%=Q`}gk_PG4~)o#L>h3XY`WiZuF}i5c!9 zokr++kBfZFH(}Nf0{VHc=4~e!K30k*K-PG9PRe-l9A2(=l&+smM+TTnDNsFcODMt* z^1zD)qHt?bb7gx3vYz3kYeKc#V61$Ek1A6T94Z{aj}IqUKB}O7nM=lOz0RKve{Y*FfF@J_ob+`mRLMEq+@fI7Fvl*1kZH*>>-9t0y|vPtx$M6 z8$)r*p^hgjj<&tc6~-G%81oe0M3}rd7TyRyF=?PJBWYMmw0HM+a!&%)Ge3M{v%!Vr|;_bt1!rXW3WJ=!oB6`B>EznlVO^acWR z0`Sskq7}$i_xVdYCIRetwPj~GOnr<&pGte@QKorq$fmvkt;dcj zj9=ee5`t62_8n(S(!o>o5_ny$``!6t1Ox25i7TBN?TQr%JPE&dQz#X8cz#I@ejH3k zpC8JMK|5g8PuuSS{Mg+PA*`dXt%Uvzk-04L`FV{7ytS4$TiI#2EY|Uiw<`3;Lq4FG z*Ub67AAz7ger`R0muDu+qP=gQheg1`SN;G^{GmMH&|0;ci|2t?p4HZJ!5gSBHkhvS z6MJQXyK|ijEGS)IHV@sK=06F8g~fvzXW;EL#zIq!{n!tp?@d-ivtT>HOn@`Hv`?4* zN&h+>_v4lFO0B+kM8usQPNg90x;UXErRf{mp3W=zk$)!D;(0X`&VKpwCHL*vGx5T! zpR1JDrW<+!7rKLkM!&@OuBO|jtXdeX=+td_?Sft3>#~YrTkk01?Mi?}@}evR<+!TZ z9gRCh-Eq!5Y`rI$mb;Xt@~P6k%3-`VfytM0Wv-oET)(zFad*7h8~{(m?wA_=VAqPbXDk6(TOkxNu29nFyDfIv@Lemnu)*vlqthN-h*yyh-&P6 z77)SbmpTJ!snr*`T6h(Bjd)-I(Z|eAcKDYkJfN}Ejcz#U4UsL76@p5CW9w*(*8wDt zOH}h=H6CKD_wlxK4*2LfH6y%hLQaBN1LnT7EIC+%7P6$&X2|5cKhJNN8hER(5*5sn zympOUd-@2hxYHmms*gEK!GeN~`~;J)bm)rH&a1VjVNc)&o^abE?+kH;7%Hh^urjG= z@=js3G5mJpVPMngR^9Qi)?iX{sDG%e+_RWv^=EHlhG^Rhh_OC;!XF>`Af_7kVT;S1 z$1KhpdieX`BUU;{LmBUDjt~!fcvy6o^qMi7sl4acGRk@2blB}wE4(rPaQ^|5icQ}f`y?}>&rF~NpWPA6%qd?ZxNxSfLrPJ>~)b``5(I%)dpA2H*+!2 z2P(E)YK6=e`yLSPHB{IswaFZ2nrc40S}>tE6lNPf_Eq_6{>gK(e9(bRC}Rzd^tTjj|{~LBWO4=>>V8iIuE`2|thO7GK?k z?t~%cTfg727uDjBoAaTUejqY<>KfO|NOQ{C?0U85a3FVEjpz@FdVZLs-RA*Gjn|ly zU>!k5?m-namLHU!^J$%_auT%R)<=EHmbn<$IUbrlzLC%>2Y7w{g;mCD$Ifxf?Pu9Z%pPAGCFX z2wR)zXXoclJt6}G1Iu)?+Sc_m^ol*}X~%=z_>TL&UKKkC+uWb@0@*i%uFgO%1hX7t zYW{twQe?4szUmH#{7LZNe8)D|*`RT?@xa;9?3Lk8_J6+q*NL89-HXbdK9fCxy=tOG+ll^zU!RI0gJPm z{CT$JLKdG`E81IH~ z%~FetmwQ-QSbI75?v+hBR8tx?mZ0(K2JGSq0G^2}vgJUV@f#!s-5gyf4N6Rvb<gr>Dtp`Un+bM$din!@a=^@!F)h1E=A??52pO;5?jANhNTS#-u%$31DgYuB z&rhqsRJDHDkEG`2=B&(V>5QT`B3+ACY^xcDxIj4mF9L~uPI<(!xq*hMAOV;6>C>l( zd-v|Sb~{~4N-h(8VZ?Z#GsVm4-YsirQ{NU!>bT%NghOaNLooXKZH-M$P5zdamc!!R z6Q`@^Ks){uzcDd4lEjt$}hHi!1efSKwMp?i2`vvbN4oiCcu}zx9+XmlhTl>c*P->o??o=Q|M1Fb=4UO%JL5owP8arHQ3r)9M>ZdKeg%frt zU?8+T4tkAWAC*M3bfLCwS6mp^jX~nq_>MQl8zA@HNEL#iB}@`p7wg=AidN+MWThW} zbHGBVF@Je`jJ*fO(%9V{I7xr&mTTng+YcHfhmnR(3!Y9JB}4?q zXdkSRzkk)&xgQBYnqti_7{iUjH-q|XkQ`esndcMTRA)DKZEfuXGqCSWy>!6@o1i0u z9VzzGFfplNfLE5!MTAsAf&G&b2!;n+T3T9i zX6^@juHpE(KY1RkCH(of5qaz0SCP zK6+)2uld!BHD;DG+&tLUZ}6HfB5(LmjS)Am5xcSVPX$D-uXdi zH2Q%v#v(EZR*FfLL@_fnt44Au1;{n;=-%ZHVcAV1)dcY}G1OzD?*TSpwe}o6D&bM} z@#Dv&go$<1_$>)=p#!R(7di-kfSj?p(c;mEQx8^vDvjAhj3<00}m+`a$k+2iH zM!tUCXJ}~X=j!D2%{Z;_6bxhx1vY?8+pekb`P}mg__`U^Af_3msHiwlS662n9v&_t z8F;VNbWx{ZRX4+=yc-Jb=8BN-Js&kiF!M&Fv8^rN=ecR{W4&~f+88UoK|jf6Cnzn1 z;4T2N(lG%<*ABt`p!wF7W7&{nS!J58mp&uz^OWXl1{imxLB`B#W1j8YX97f%LST%I0}j*k95 zsuy8sZ+3rGuS&d#S;<8?Y<~KynK<+@+4FXm=(yE-tRX&(+cK9@Xv<+mk9R0XE@!Q|ZA8=fk&v zsJDG_L@b8^y_o~s7`AI@YnSL2u!R*NWyrU1s7dma;+pjRg{1OW=}c6Fc=@vZgP{Dx8$@& z^_*SWCkO*zo&#@qqGXyx7g3iw$rLy##sW^y zSJ_JtMXAnM}Xy$r|j_!YxwE4{G@oq(+3f|)eivD>(6%3K<)$6 zgLS9t>gbf$%W7-*=#YCWVPdL3YD{NCkcR+zd2;k*(k9^`H@d(EJx~={1`?sZc=xC! zciS2s4PwhdK|~iED!subBp3;*ty{OA92y=rdwzaj8I@>Vs+|o1c9v>@S`!s&y`n~J;e_C}kwj{%n=?AqbLTgt z`3OieOitK>X!A^q&yd)9^l_x^?2<~EZD}F|hNRS49=XeOdvC;?RvHfp!j5_Z0Ct__PW-q5Z=yET zBvHD)=kdF?ar%Yew0DjLihvGvUSQQNmph142};xs5}2(U9j=u!Q#YQhJE$JVQtrVgBcV@uu z^c*IxCKQT6CxJQLW>7_Tjatpj0|EkOZyY#fYPv3;RmUL{UMvah+s$NRhz}UGz;oct ze{FL#A{sc2@lM}C92Pq|I;x+gaJR+QNHq~O?rdY#*?qERL$CaP!7oS6iznTK_fGx##{ zsWn_iHY!mDi{>@-)FS9Q$U;&^PB*2SOgs{ue%_)b9SJ<>A?Un+jk%Yny%}bJmU>u6 z#>P5}&u5jL1r@%vx;V4DV&X>TWC2EH)Gma;h!}Zl?}_#tTMLW+Ie;nOv&k4Pk4_C< zlS%B81rPPS3uJXqtJ4%|=to449z80F03$6#Fp_!=_!39JVjiIp2F*G6Hku}$X$%oI zNG|0y5Hw6`Bo`1-I}W&m;mNT|gm!nIH3vUd z_!i0Z8H!gcNB5mmjItJg1bn6u>creK?~X%6%Fp;h^73GHUBZMpzM=RJzIefULD*dW zIXkgpL+fnCo?_U~c0V~eNUFKSX$Pnya}@EyF*kvrZ0)EGf}EJs=AGZZeXFdgsmT}b zZhpwE4`XaGT^UdY3#0;eL&Ev+15A9TvZo>m+&1qcIOhH^AF0q|P(6@evmt_%wnfhfp5CD#01ldeK^E^bju4!3Xz7=;`UR^wV4NtP5(& zwZn(G-qh^Z25>Vq3fP4TXHp}A_1CKAZflUtHq8V=_RY`Ygk~?~3;HBBEf{Gt_o;%r{?UtFw4lZVW8vKfnyDMZ zrwyvO=w=rdA~}_VR&pJ3i)Q*Q6IQ?HMA-)0B)o-^VGKwmH|wkr{L-wv#DSmcm6a92 zSoMK;3Y(?Nl7IQ3>#S4&1~A_Pc)oy6K=?5QHi;JQkwB&`lhl?rEEDd4Tobzfuan;K>XcvI*mlIvBapITf6l9Y4={17va(44RUE) zbHLc-9Vnsq3_SP?QF{Iwvq4c0zzOHSFgWRpr(VI%)$wsc9kk6T24;nRetwnl@$n4| zjErUm%@Kw^)2jWg+PM?+6JR2Ooda!*jI5`{P>r~`t1pnGS(0{8g1hbUMkeI~VKVS` z1uMe-m<*I*sD07Ez_$bx_r02J3tHOYor(pe`JOUBN`#r_<^j-2@zh(lZ3`b78q&IO z_+qK6-+)xV3fqDi!#C(Af|USBb5lCU5xFKf&D=x-OaFo)D>kE~f>x+OVpkY4iF1(6p+=wq^i(MhVd+)C(!nhWJmmJw z265mTV{a=W@thVUf!x}~Gk@!Mvf3!U`ud3vL_Ksgu*03Nt&u1EcNsaN1L8(F=No6j zSOJ7wB@%ej5&!wRpfXExcW}ktWC)$8fGsLcXGqkh3c$&Kpp(=A;7c4ngVOi`J4_cw zn25ZEuVmKd$OZ)R-2Nw>bAcC_&s~7*@7^e$wUR+a=07?XMfTG`qJ31nad$%?WzFAmUOtbXT3$F*uQTdCu zxw-E|>u3QL$TqZOB9ttUjqB^{Ya~u{-rO;!8CXLx-_i=f`~y@#EiZZ*VMZg{u{Unq zcvQv6rSx#rg}uJxmc=)`(Zzr^!G)Jwayd5H1pL+>AoNBgxU-eZ>M~TICZazD-1pEp5hbb?BBv&9RQH1R;X@eD}!2MCn4Yay$}-x|rPq1-EC(ODLjqXuy>h zu7f|7Yv`$+*yQBo=`UZtysD_EU{#uN(@F{A4!v^tDdr`B6&iplu?9^C1j97{-Rsj~ z0?Usvs3SY0G{z)m+?LcdQI*0#$?4=nGscY8F$Oy>H)y#d4=`q7+rNK*BBF#$hDu1; zY;B-cCeUuai4%_{dLboB7BQib01XjBe69u~j@aq`KM?Ir3akP4nuLUphNfo8UVoE6 zmpUut_$%5I?G)<(zfcQKjiE^zVLxavyhOz3YGi!=O|l5V8Ird791FGSv$|O)RyF7+ zCtE=W=r`^~BT0frG)@p(+TtMVS$m^DlVtD`5`=v*%RlpbASeAoXW9D&PkB(Hf*f|Z z|53*-gp*Mz?Pdc%2`MSTPtMt|?7|&UzH|LNqg8?iXqdhP#$@fW`zrDfFu@^oM;s?g zhl*Voe2(s{>`R;++zPnR13{wWajqDN;(k{@Za@*M10b&s2~Z&+0_F^{Ns|5~T{8H) ze8m%d{Wz3Aqz<~kz=aIcQ^yc-3$4S1&CJYv^zPj|4|oh2@rHl!haQ`0x(!r6+Qo+` z@9yArH9^w#`j|PmE>%{tIkjPrGpoOAMsqLOu>QCQ4buoTf*F30`w&qyVE!A4V5LD! z>IiXBBRRqd8LY?exR=bOFla0ImhdZr_-hMro7~u&aYPmK8yGV8l>kHJfOuPSPPq;s z-Mc;8f`GOiKG?Mq29dwZ`BCY_L8N3aGGhNSB?J-sU+TOtF5Wk6rR-x5H9UGa+!eVh z)8LW{a|>ijp{?HjNN#r@Bb~R~_kf6vAJhJR{fWNTo}L`PlAts&9{5-Cd%VgPgUC<&+#hbY^oJ+F z;L6Olmw!Y;?So5;p4$L~a2#;G zQ(MypsWs3>LG(m;P5pA~R@Lsevw?4#2s{FGg@!wvK{8?&hIe@BiSd1IQPXLNaa zuE2JnIGfWrlD%*nCF%+=>KQpkdxYZ9QMo^(kV+GPLSF0yxAo9qfU-w(2!$lcGY|Ln z?c2tIaK@DP=O2A|K^Q}VOMrAQ_!^B#sv|8{;uuoSi4iK~rQkxI2{g%XQb{~S>KL?u z+F62PrY}ts0XdsxWO9;AF=%BG%muu@bVzipjXa7< zJ!`cD>hV+vi*P<@N1jlv_H%2*mv6IxbkhYBLd_c*L~=kGg>9iB6p-l&o!4cPDiZn2 z*_kcD?(yxYG5)EsvbC}0wI5mQKji!G6t3O=_9!X? z_69V#e5AKE9KCU+9p^F~*J<``MWw8W^e&zRQLOaJHQDh12M>c45c|isTR5Yr zq@8rkm-2~<1M~FY1)t!y(~A+JF$M`KvA$0B&$4YuUVZ8syxdvlJNhI(Xno67nQ~=i){}R7?JnC^ zUtk<~WwGK2Meoz#3Z43P7pPUe;nkXS`+tDj-rbGR$9A`H$=P4?%NHXpR{j*CJ)#cU z)9y}r@{32ENF$~=%c4s4{4gv^dtfb36b;!)-HU~0MXg|1gMMNxj*6xW+zR#DvR@mS zD}kaMX%zNw`cLhP^#6kb%L0;wA=OwOfimcYjv=iMS$nXG)*ICPJkYR zhq!n>nR;-^BoTmRsmDY6NXUhvFxCn%V)c;)(K^~NCcBdW&fQLQ+*A)y8h3$w?w&Q; z_^J;gmZ$Co9w6b6iNwqJFmW1uB|-h9Gxx9O+W^YzpA{0nII$U5(9oAR_U4BDdj9_& zW>PI7Z)(r+&%X?F-bniO31C&HvGqwZtm66jmUe12&<0OR@;8F8YoOd99WZk*MWZy6 z8m=iY{J^l}mS)EZj(~=*Qn62zt*?!Y8E+&#Ns-3xl_#0Rj%ec*)Rrw<*h^Qwh?JF0 zPX6adU9UnfK3$p^F;`OQXJRn2QKHu&#YyO>p`=RxmJ6Fm*Rt$Y8ry(U!Aeg z7kt;7zyWTY;c9DZuZ!vln-tC7;#yy>gi;eeyDo0ec=O z@O%?|QGGM}P+$+Dg6J)vTQa8sHc_v2CMiIf>~aC!E#(v1XmE)`u%vJgvU0>LlLt7- zPJ=Wgn8thQU!Y_6L?)kyaMtDvOelC3s_r}qp|Hcx0t#n`^oVw`VJ}R-UPIe(#VAuC zm7cDfBvOHQ?F7%FIimY3!A-zKo05#+Yf&T)j8*}F8fy*T_a_6=A)a<;1T%{thd{j~xXWVzfR3{&ENjJHKnuzzhC2D23a5{O#uN z0w9$hSwUp4*-WS#d7bPvRXPQaQfFwyw~BKJ91`DpJ&K2SJO3jO?PInzQg^@bNDmqQ zN!=Zv5zN7{eC*GE949Bcs!ki1p%K@$@rjEDh0?=`lU$#G+Z*llGzR`2tiKK}u7-r{ z$=3Mw{dp9swWsShqXsnEocf6FPbL87swE*IxiUV#8Gz8x7*N&`hW#5&S@6{g5w!Vu zy~(d*88QpRa7)$#5uu~WhD1OjWOpXwbSya{k%KBZwZWq;vM&(x1sPxDV4D~`$zXf)st64e8ElimHo1d=w*j&*kbQyd3uIp)q9*+PL2}_q z#7^YGlU#U`MZT%OZIf_q1rm!;+-`W~(>QY$SlmK`_a#%gQ# z^>ekG1+CW0tgjC1o2{;_EPWSAR`j1=YJRG{zBbjSyfQtCUA##kMoA9KSk~NEkuuo} z{tVGrl8o!x`q!3pv$chR$gH5nwPg|IB9T_s;~}`-74_C$DysK%U4vyfxnC#)Zxw<*b-RGU6&)Ty{IRS}h985= z;%7}oh<~+bGd>p_Mw^@LnI-(4M#At6cNXYUAS<5acZI>98t-As&ZP5$|1#vJa^Z9O zo&i&eX{#d$h45)69gTrLwIcB^iErMAbUTm1$<>a1A@D|cp%0i$znc6j#YX|f9AD;0 ze%CSnYF#>D^1`u(Un$-XD84h}CyNj?{v6x|z~tYl!@p8|2T*+KN(t#Nd4%}s`x`Jx zR?DXS5YpNIO)V)Vfmk5pMP2OV+U%)S^e=iKSRMHVL7?Opgjhg+LG}x>JN$p}f`G%Q aGDVNyao#Jqjo~;c&3W^)&B#wYQnAn literal 0 HcmV?d00001 diff --git a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift index 450be391d..beec6221b 100644 --- a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift +++ b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift @@ -217,7 +217,7 @@ class AttachmentApprovalViewController: UIViewController, OWSAudioAttachmentPlay private func createGenericPreview(attachmentPreviewView: UIView) { var subviews = [UIView]() - let imageView = createHeroImageView(imageName: "file-thin-black-large") + let imageView = createHeroImageView(imageName: "file-thin-black-filled-large") subviews.append(imageView) if let fileNameLabel = createFileNameLabel() { @@ -245,6 +245,11 @@ class AttachmentApprovalViewController: UIViewController, OWSAudioAttachmentPlay let imageView = UIImageView(image:image) imageView.layer.minificationFilter = kCAFilterTrilinear imageView.layer.magnificationFilter = kCAFilterTrilinear + imageView.layer.shadowColor = UIColor.black.cgColor + let shadowScaling = 5.0 + imageView.layer.shadowRadius = CGFloat(2.0 * shadowScaling) + imageView.layer.shadowOpacity = 0.25 + imageView.layer.shadowOffset = CGSize(width: 0.75 * shadowScaling, height: 0.75 * shadowScaling) imageView.autoSetDimension(.width, toSize:imageSize) imageView.autoSetDimension(.height, toSize:imageSize) diff --git a/Signal/src/ViewControllers/SelectThreadViewController.h b/Signal/src/ViewControllers/SelectThreadViewController.h index b09abf4c4..f420843c0 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.h +++ b/Signal/src/ViewControllers/SelectThreadViewController.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)canSelectBlockedContact; -- (nullable UIView *)createHeader:(UIView *)superview; +- (nullable UIView *)createHeaderWithSearchBar:(UISearchBar *)searchBar; @end diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index 0230aa8b3..807346731 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -92,7 +92,16 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(self.delegate); - UIView *header = [self.delegate createHeader:self.view]; + // Search + UISearchBar *searchBar = [UISearchBar new]; + _searchBar = searchBar; + searchBar.searchBarStyle = UISearchBarStyleMinimal; + searchBar.delegate = self; + searchBar.placeholder = NSLocalizedString(@"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT", @""); + searchBar.backgroundColor = [UIColor whiteColor]; + [searchBar sizeToFit]; + + UIView *header = [self.delegate createHeaderWithSearchBar:searchBar]; // Table _tableViewController = [OWSTableViewController new]; @@ -100,21 +109,13 @@ NS_ASSUME_NONNULL_BEGIN _tableViewController.contents = [OWSTableContents new]; [self.view addSubview:self.tableViewController.view]; [_tableViewController.view autoPinWidthToSuperview]; + [_tableViewController.view autoPinToTopLayoutGuideOfViewController:self withInset:0]; if (header) { - [_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:header]; + _tableViewController.tableView.tableHeaderView = header; } else { - [_tableViewController.view autoPinToTopLayoutGuideOfViewController:self withInset:0]; + _tableViewController.tableView.tableHeaderView = searchBar; } [_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 diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.m b/Signal/src/ViewControllers/SendExternalFileViewController.m index bbcac0a93..1c46aee18 100644 --- a/Signal/src/ViewControllers/SendExternalFileViewController.m +++ b/Signal/src/ViewControllers/SendExternalFileViewController.m @@ -57,22 +57,21 @@ NS_ASSUME_NONNULL_BEGIN return NO; } -- (nullable UIView *)createHeader:(UIView *)superview +- (nullable UIView *)createHeaderWithSearchBar:(UISearchBar *)searchBar { - OWSAssert(superview) + OWSAssert(searchBar) const CGFloat imageSize - = ScaleFromIPhone5To7Plus(40, 40); - const CGFloat imageLabelSpacing = ScaleFromIPhone5To7Plus(5, 5); - const CGFloat titleVSpacing = ScaleFromIPhone5To7Plus(10, 10); + = ScaleFromIPhone5To7Plus(40, 50); + const CGFloat imageLabelSpacing = ScaleFromIPhone5To7Plus(5, 8); + const CGFloat titleVSpacing = ScaleFromIPhone5To7Plus(10, 15); const CGFloat contentVMargin = ScaleFromIPhone5To7Plus(20, 20); UIView *header = [UIView new]; - [superview addSubview:header]; - [header autoPinWidthToSuperview]; - [header autoPinToTopLayoutGuideOfViewController:self withInset:0]; + header.backgroundColor = [UIColor whiteColor]; UIView *titleLabel = [self createTitleLabel]; + [titleLabel sizeToFit]; [header addSubview:titleLabel]; [titleLabel autoHCenterInSuperview]; [titleLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:contentVMargin]; @@ -80,14 +79,17 @@ NS_ASSUME_NONNULL_BEGIN UIView *fileView = [UIView new]; [header addSubview:fileView]; [fileView autoHCenterInSuperview]; - [fileView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:contentVMargin]; [fileView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:titleLabel withOffset:titleVSpacing]; - UIImage *image = [UIImage imageNamed:@"file-thin-black-large"]; + UIImage *image = [UIImage imageNamed:@"file-thin-black-filled-large"]; OWSAssert(image); UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; imageView.layer.minificationFilter = kCAFilterTrilinear; imageView.layer.magnificationFilter = kCAFilterTrilinear; + imageView.layer.shadowColor = [UIColor blackColor].CGColor; + imageView.layer.shadowRadius = 2.f; + imageView.layer.shadowOpacity = 0.2f; + imageView.layer.shadowOffset = CGSizeMake(0.75f, 0.75f); [fileView addSubview:imageView]; [imageView autoSetDimension:ALDimensionWidth toSize:imageSize]; [imageView autoSetDimension:ALDimensionHeight toSize:imageSize]; @@ -101,6 +103,17 @@ NS_ASSUME_NONNULL_BEGIN [fileNameLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:imageView withOffset:imageLabelSpacing]; [fileNameLabel autoPinEdgeToSuperviewEdge:ALEdgeRight]; + [header addSubview:searchBar]; + [searchBar autoPinWidthToSuperview]; + [searchBar autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:fileView withOffset:contentVMargin]; + [searchBar autoPinEdgeToSuperviewEdge:ALEdgeBottom]; + + // UITableViewController.tableHeaderView must have its height set. + header.frame = CGRectMake(0, + 0, + 0, + (contentVMargin * 2 + titleLabel.frame.size.height + titleVSpacing + imageSize + searchBar.frame.size.height)); + return header; } @@ -125,7 +138,7 @@ NS_ASSUME_NONNULL_BEGIN UILabel *label = [UILabel new]; label.text = [self formattedFileName]; label.textColor = [UIColor ows_materialBlueColor]; - label.font = [UIFont ows_regularFontWithSize:16.f]; + label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(16.f, 20.f)]; label.lineBreakMode = NSLineBreakByTruncatingMiddle; return label; } @@ -137,7 +150,7 @@ NS_ASSUME_NONNULL_BEGIN label.text = NSLocalizedString(@"SEND_EXTERNAL_FILE_HEADER_TITLE", @"Header title for the 'send external file' view."); label.textColor = [UIColor blackColor]; - label.font = [UIFont ows_mediumFontWithSize:18.f]; + label.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(18.f, 20.f)]; return label; } diff --git a/Signal/src/ViewControllers/SignalAttachment.swift b/Signal/src/ViewControllers/SignalAttachment.swift index 584948a0b..76e89810c 100644 --- a/Signal/src/ViewControllers/SignalAttachment.swift +++ b/Signal/src/ViewControllers/SignalAttachment.swift @@ -12,6 +12,7 @@ enum SignalAttachmentError: Error { case couldNotParseImage case couldNotConvertToJpeg case invalidFileFormat + case unknownType } extension SignalAttachmentError: LocalizedError { @@ -29,6 +30,8 @@ extension SignalAttachmentError: LocalizedError { return NSLocalizedString("ATTACHMENT_ERROR_COULD_NOT_CONVERT_TO_JPEG", comment: "Attachment error message for image attachments which could not be converted to JPEG") case .invalidFileFormat: return NSLocalizedString("ATTACHMENT_ERROR_INVALID_FILE_FORMAT", comment: "Attachment error message for attachments with an invalid file format") + case .unknownType: + return NSLocalizedString("ATTACHMENT_ERROR_UNKNOWN_TYPE", comment: "Attachment error message for attachments with an invalid file format") } } } @@ -111,6 +114,10 @@ class SignalAttachment: NSObject { self.dataUTI = dataUTI self.filename = filename super.init() + + if self.mimeType == nil { + error = .unknownType + } } // MARK: Methods diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 954f72407..5b985982d 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -94,6 +94,9 @@ /* Attachment error message for attachments without any data */ "ATTACHMENT_ERROR_MISSING_DATA" = "Attachment is empty."; +/* Attachment error message for attachments with an invalid file format */ +"ATTACHMENT_ERROR_UNKNOWN_TYPE" = "Attachment is of invalid type"; + /* Accessibility hint describing what you can do with the attachment button */ "ATTACHMENT_HINT" = "Choose or take a picture and then send it"; From 8a8b10b68863c057eabde949ee545b2548c80cc8 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Apr 2017 15:55:01 -0400 Subject: [PATCH 8/8] Respond to CR. // FREEBIE --- Signal/src/ViewControllers/SendExternalFileViewController.m | 3 +-- Signal/src/ViewControllers/ThreadViewHelper.h | 4 ++++ Signal/src/ViewControllers/ThreadViewHelper.m | 4 ++++ Signal/src/views/ContactTableViewCell.m | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Signal/src/ViewControllers/SendExternalFileViewController.m b/Signal/src/ViewControllers/SendExternalFileViewController.m index 1c46aee18..501f83ace 100644 --- a/Signal/src/ViewControllers/SendExternalFileViewController.m +++ b/Signal/src/ViewControllers/SendExternalFileViewController.m @@ -65,7 +65,7 @@ NS_ASSUME_NONNULL_BEGIN = ScaleFromIPhone5To7Plus(40, 50); const CGFloat imageLabelSpacing = ScaleFromIPhone5To7Plus(5, 8); const CGFloat titleVSpacing = ScaleFromIPhone5To7Plus(10, 15); - const CGFloat contentVMargin = ScaleFromIPhone5To7Plus(20, 20); + const CGFloat contentVMargin = 20; UIView *header = [UIView new]; header.backgroundColor = [UIColor whiteColor]; @@ -139,7 +139,6 @@ NS_ASSUME_NONNULL_BEGIN label.text = [self formattedFileName]; label.textColor = [UIColor ows_materialBlueColor]; label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(16.f, 20.f)]; - label.lineBreakMode = NSLineBreakByTruncatingMiddle; return label; } diff --git a/Signal/src/ViewControllers/ThreadViewHelper.h b/Signal/src/ViewControllers/ThreadViewHelper.h index 5d88fce1a..d6d98b638 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.h +++ b/Signal/src/ViewControllers/ThreadViewHelper.h @@ -2,6 +2,8 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +NS_ASSUME_NONNULL_BEGIN + @protocol ThreadViewHelperDelegate - (void)threadListDidChange; @@ -24,3 +26,5 @@ @property (nonatomic, readonly) NSMutableArray *threads; @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ThreadViewHelper.m b/Signal/src/ViewControllers/ThreadViewHelper.m index cfb12b421..b8c37fbce 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.m +++ b/Signal/src/ViewControllers/ThreadViewHelper.m @@ -10,6 +10,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @interface ThreadViewHelper () @property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; @@ -134,3 +136,5 @@ } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/views/ContactTableViewCell.m b/Signal/src/views/ContactTableViewCell.m index 82fd906ee..12286a83d 100644 --- a/Signal/src/views/ContactTableViewCell.m +++ b/Signal/src/views/ContactTableViewCell.m @@ -53,10 +53,10 @@ NSString *const kContactsTable_CellReuseIdentifier = @"kContactsTable_CellReuseI const CGFloat kAvatarSize = 40.f; _avatarView = [UIImageView new]; _avatarView.image = [UIImage imageNamed:@"empty-group-avatar"]; + _avatarView.contentMode = UIViewContentModeScaleToFill; // applyRoundedBorderToImageView requires the avatar to have // the correct size. _avatarView.frame = CGRectMake(0, 0, kAvatarSize, kAvatarSize); - _avatarView.contentMode = UIViewContentModeScaleToFill; _avatarView.layer.minificationFilter = kCAFilterTrilinear; _avatarView.layer.magnificationFilter = kCAFilterTrilinear; [self.contentView addSubview:_avatarView];