diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c0888091c..4795aec08 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -50,7 +50,6 @@ 34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8551E8DF1700035BE1A /* NewGroupViewController.m */; }; 34B3F8861E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8571E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m */; }; 34B3F8871E8DF1700035BE1A /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */; }; - 34B3F8881E8DF1700035BE1A /* OversizeTextMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */; }; 34B3F8891E8DF1700035BE1A /* OWSConversationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */; }; 34B3F88A1E8DF1700035BE1A /* OWSLinkDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F85E1E8DF1700035BE1A /* OWSLinkDeviceViewController.m */; }; 34B3F88B1E8DF1700035BE1A /* OWSLinkedDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8601E8DF1700035BE1A /* OWSLinkedDevicesTableViewController.m */; }; @@ -76,7 +75,7 @@ 34C6B0AC1FA0E46F00D35993 /* test-mp4.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 34C6B0A81FA0E46F00D35993 /* test-mp4.mp4 */; }; 34C6B0AE1FA0E4AA00D35993 /* test-jpg.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 34C6B0AD1FA0E4AA00D35993 /* test-jpg.jpg */; }; 34CA1C251F706B5400E51C51 /* NSAttributedString+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CA1C241F706B5400E51C51 /* NSAttributedString+OWS.m */; }; - 34CA1C271F7156F300E51C51 /* MessageMetadataViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CA1C261F7156F300E51C51 /* MessageMetadataViewController.swift */; }; + 34CA1C271F7156F300E51C51 /* MessageDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */; }; 34CA1C291F7164F700E51C51 /* MediaMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CA1C281F7164F700E51C51 /* MediaMessageView.swift */; }; 34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */; }; 34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */; }; @@ -240,8 +239,8 @@ 45DF5DF31DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; }; 45E2E9201E153B3D00457AA0 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E2E91F1E153B3D00457AA0 /* Strings.swift */; }; 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; - 45E615161E8C590B0018AD52 /* DisplayableTextFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */; }; - 45E615171E8C59100018AD52 /* DisplayableTextFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */; }; + 45E615161E8C590B0018AD52 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E615151E8C590B0018AD52 /* DisplayableText.swift */; }; + 45E615171E8C59100018AD52 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E615151E8C590B0018AD52 /* DisplayableText.swift */; }; 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; }; 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; 45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; @@ -477,7 +476,6 @@ 34B3F8571E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = ""; }; 34B3F8581E8DF1700035BE1A /* NotificationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsViewController.h; sourceTree = ""; }; 34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsViewController.m; sourceTree = ""; }; - 34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OversizeTextMessageViewController.swift; sourceTree = ""; }; 34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewController.h; sourceTree = ""; }; 34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSConversationSettingsViewController.m; sourceTree = ""; }; 34B3F85D1E8DF1700035BE1A /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = ""; }; @@ -521,7 +519,7 @@ 34C6B0AD1FA0E4AA00D35993 /* test-jpg.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "test-jpg.jpg"; sourceTree = ""; }; 34CA1C231F706B5400E51C51 /* NSAttributedString+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+OWS.h"; sourceTree = ""; }; 34CA1C241F706B5400E51C51 /* NSAttributedString+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+OWS.m"; sourceTree = ""; }; - 34CA1C261F7156F300E51C51 /* MessageMetadataViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageMetadataViewController.swift; sourceTree = ""; }; + 34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDetailViewController.swift; sourceTree = ""; }; 34CA1C281F7164F700E51C51 /* MediaMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageView.swift; sourceTree = ""; }; 34CCAF361F0C0599004084F4 /* AppUpdateNag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppUpdateNag.h; sourceTree = ""; }; 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = ""; }; @@ -709,7 +707,7 @@ 45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 45E2E91F1E153B3D00457AA0 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = Strings.swift; path = UserInterface/Strings.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarqueeLabel.swift; sourceTree = ""; }; - 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilter.swift; sourceTree = ""; }; + 45E615151E8C590B0018AD52 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = ""; }; 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = ""; }; 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = ""; }; 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSessionTest.swift; sourceTree = ""; }; @@ -1036,7 +1034,7 @@ 34B3F84D1E8DF1700035BE1A /* LockInteractionController.h */, 34B3F84E1E8DF1700035BE1A /* LockInteractionController.m */, 34CA1C281F7164F700E51C51 /* MediaMessageView.swift */, - 34CA1C261F7156F300E51C51 /* MessageMetadataViewController.swift */, + 34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */, 34D9134C1F66DB7C00722898 /* ModalActivityIndicatorViewController.swift */, 34B3F84F1E8DF1700035BE1A /* NewContactThreadViewController.h */, 34B3F8501E8DF1700035BE1A /* NewContactThreadViewController.m */, @@ -1048,7 +1046,6 @@ 34B3F8571E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m */, 34B3F8581E8DF1700035BE1A /* NotificationSettingsViewController.h */, 34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */, - 34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */, 34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */, 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */, 34533F161EA8D2070006114F /* OWSAudioAttachmentPlayer.h */, @@ -1460,7 +1457,7 @@ B90418E4183E9DD40038554A /* DateUtil.h */, B90418E5183E9DD40038554A /* DateUtil.m */, 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */, - 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */, + 45E615151E8C590B0018AD52 /* DisplayableText.swift */, 76EB04EA18170B33006006FC /* FunctionalUtil.h */, 76EB04EB18170B33006006FC /* FunctionalUtil.m */, 455AC69A1F4F79E500134004 /* ImageCache.swift */, @@ -2286,16 +2283,15 @@ 45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */, 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */, 45666EC61D99483D008FE134 /* OWSAvatarBuilder.m in Sources */, - 45E615161E8C590B0018AD52 /* DisplayableTextFilter.swift in Sources */, + 45E615161E8C590B0018AD52 /* DisplayableText.swift in Sources */, 34B3F88A1E8DF1700035BE1A /* OWSLinkDeviceViewController.m in Sources */, 34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */, 76EB068618170B34006006FC /* ContactTableViewCell.m in Sources */, 3497DBEF1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m in Sources */, - 34B3F8881E8DF1700035BE1A /* OversizeTextMessageViewController.swift in Sources */, 452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */, 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */, 34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */, - 34CA1C271F7156F300E51C51 /* MessageMetadataViewController.swift in Sources */, + 34CA1C271F7156F300E51C51 /* MessageDetailViewController.swift in Sources */, 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */, 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */, 34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */, @@ -2470,7 +2466,7 @@ 451DA3CB1F148AAD008E2423 /* CallViewController.swift in Sources */, 456F6E201E2411A000FD2210 /* CallService.swift in Sources */, 45A663C61F92EC760027B59E /* GroupTableViewCell.swift in Sources */, - 45E615171E8C59100018AD52 /* DisplayableTextFilter.swift in Sources */, + 45E615171E8C59100018AD52 /* DisplayableText.swift in Sources */, B660F6BB1C29868000687D6E /* OWSContactsManagerTest.m in Sources */, 45A6DAD71EBBF85500893231 /* ReminderView.swift in Sources */, B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */, diff --git a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h index a94f6b944..22b18ae08 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/ConversationViewCell.h @@ -20,14 +20,14 @@ NS_ASSUME_NONNULL_BEGIN imageView:(UIView *)imageView; - (void)didTapVideoViewItem:(ConversationViewItem *)viewItem attachmentStream:(TSAttachmentStream *)attachmentStream; - (void)didTapAudioViewItem:(ConversationViewItem *)viewItem attachmentStream:(TSAttachmentStream *)attachmentStream; -- (void)didTapOversizeTextMessage:(NSString *)displayableText attachmentStream:(TSAttachmentStream *)attachmentStream; +- (void)didTapTruncatedTextMessage:(ConversationViewItem *)conversationItem; - (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem attachmentPointer:(TSAttachmentPointer *)attachmentPointer; - (void)didTapFailedOutgoingMessage:(TSOutgoingMessage *)message; - (void)didPanWithGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer viewItem:(ConversationViewItem *)conversationItem; -- (void)showMetadataViewForMessage:(TSMessage *)message; +- (void)showMetadataViewForViewItem:(ConversationViewItem *)conversationItem; #pragma mark - System Cell diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index cbcec0aec..4901da5ec 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -73,6 +73,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BubbleMaskingView *payloadView; @property (nonatomic) UILabel *dateHeaderLabel; @property (nonatomic) UITextView *textView; +@property (nonatomic, nullable) UILabel *tapForMoreLabel; @property (nonatomic, nullable) UIImageView *bubbleImageView; @property (nonatomic, nullable) AttachmentUploadView *attachmentUploadView; @property (nonatomic, nullable) UIImageView *stillImageView; @@ -130,7 +131,6 @@ NS_ASSUME_NONNULL_BEGIN self.textView = [UITextView new]; // Honor dynamic type in the message bodies. self.textView.font = [self textMessageFont]; - self.textView.font = [UIFont ows_regularFontWithSize:16.f]; self.textView.backgroundColor = [UIColor clearColor]; self.textView.opaque = NO; self.textView.editable = NO; @@ -183,17 +183,27 @@ NS_ASSUME_NONNULL_BEGIN return [UIFont ows_dynamicTypeBodyFont]; } +- (UIFont *)tapForMoreFont +{ + return [UIFont ows_regularFontWithSize:12.f]; +} + +- (CGFloat)tapForMoreHeight +{ + return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25); +} + - (OWSMessageCellType)cellType { return self.viewItem.messageCellType; } -- (nullable NSString *)textMessage +- (nullable DisplayableText *)displayableText { // This should always be valid for the appropriate cell types. - OWSAssert(self.viewItem.textMessage); + OWSAssert(self.viewItem.displayableText); - return self.viewItem.textMessage; + return self.viewItem.displayableText; } - (nullable TSAttachmentStream *)attachmentStream @@ -571,7 +581,7 @@ NS_ASSUME_NONNULL_BEGIN { self.bubbleImageView.hidden = NO; self.textView.hidden = NO; - self.textView.text = self.textMessage; + self.textView.text = self.displayableText.displayText; UIColor *textColor = [self textColor]; self.textView.textColor = textColor; self.textView.font = [self textMessageFont]; @@ -592,12 +602,34 @@ NS_ASSUME_NONNULL_BEGIN = (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent); } - self.contentConstraints = @[ - [self.textView autoPinLeadingToSuperviewWithMargin:self.textLeadingMargin], - [self.textView autoPinTrailingToSuperviewWithMargin:self.textTrailingMargin], - [self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.textVMargin], - [self.textView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.textVMargin], - ]; + if (self.displayableText.isTextTruncated) { + self.tapForMoreLabel = [UILabel new]; + self.tapForMoreLabel.text = NSLocalizedString(@"CONVERSATION_VIEW_OVERSIZE_TEXT_TAP_FOR_MORE", + @"Indicator on truncated text messages that they can be tapped to see the entire text message."); + self.tapForMoreLabel.font = [self tapForMoreFont]; + self.tapForMoreLabel.textColor = [textColor colorWithAlphaComponent:0.85]; + self.tapForMoreLabel.textAlignment = [self.tapForMoreLabel textAlignmentUnnatural]; + [self.bubbleImageView addSubview:self.tapForMoreLabel]; + + self.contentConstraints = @[ + [self.textView autoPinLeadingToSuperviewWithMargin:self.textLeadingMargin], + [self.textView autoPinTrailingToSuperviewWithMargin:self.textTrailingMargin], + [self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.textVMargin], + + [self.tapForMoreLabel autoPinLeadingToSuperviewWithMargin:self.textLeadingMargin], + [self.tapForMoreLabel autoPinTrailingToSuperviewWithMargin:self.textTrailingMargin], + [self.tapForMoreLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.textView], + [self.tapForMoreLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.textVMargin], + [self.tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight], + ]; + } else { + self.contentConstraints = @[ + [self.textView autoPinLeadingToSuperviewWithMargin:self.textLeadingMargin], + [self.textView autoPinTrailingToSuperviewWithMargin:self.textTrailingMargin], + [self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.textVMargin], + [self.textView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.textVMargin], + ]; + } } - (void)loadForStillImageDisplay @@ -772,11 +804,12 @@ NS_ASSUME_NONNULL_BEGIN CGFloat textVMargin = self.textVMargin; const int maxTextWidth = (int)floor(maxMessageWidth - (leftMargin + rightMargin)); - self.textView.text = self.textMessage; + self.textView.text = self.displayableText.displayText; self.textView.font = [self textMessageFont]; CGSize textSize = [self.textView sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]; + CGFloat tapForMoreHeight = (self.displayableText.isTextTruncated ? [self tapForMoreHeight] : 0.f); cellSize = CGSizeMake((CGFloat)ceil(textSize.width + leftMargin + rightMargin), - (CGFloat)ceil(textSize.height + textVMargin * 2)); + (CGFloat)ceil(textSize.height + textVMargin * 2 + tapForMoreHeight)); break; } case OWSMessageCellType_StillImage: @@ -907,6 +940,8 @@ NS_ASSUME_NONNULL_BEGIN self.textView.text = nil; self.textView.hidden = YES; self.textView.dataDetectorTypes = UIDataDetectorTypeNone; + [self.tapForMoreLabel removeFromSuperview]; + self.tapForMoreLabel = nil; self.footerLabel.text = nil; self.footerLabel.hidden = YES; self.bubbleImageView.image = nil; @@ -990,9 +1025,11 @@ NS_ASSUME_NONNULL_BEGIN switch (self.cellType) { case OWSMessageCellType_TextMessage: - break; case OWSMessageCellType_OversizeTextMessage: - [self.delegate didTapOversizeTextMessage:self.textMessage attachmentStream:self.attachmentStream]; + if (self.displayableText.isTextTruncated) { + [self.delegate didTapTruncatedTextMessage:self.viewItem]; + return; + } break; case OWSMessageCellType_StillImage: [self.delegate didTapImageViewItem:self.viewItem @@ -1093,7 +1130,7 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); - [self.delegate showMetadataViewForMessage:self.message]; + [self.delegate showMetadataViewForViewItem:self.viewItem]; } - (BOOL)canBecomeFirstResponder diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index c73c92901..328ee074c 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2040,18 +2040,18 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { [self.audioAttachmentPlayer play]; } -- (void)didTapOversizeTextMessage:(NSString *)displayableText attachmentStream:(TSAttachmentStream *)attachmentStream +- (void)didTapTruncatedTextMessage:(ConversationViewItem *)conversationItem { OWSAssert([NSThread isMainThread]); - OWSAssert(displayableText); - OWSAssert(attachmentStream); + OWSAssert(conversationItem); + OWSAssert([conversationItem.interaction isKindOfClass:[TSMessage class]]); - // Tapping on incoming and outgoing "oversize text messages" should show the - // "oversize text message" view. - OversizeTextMessageViewController *messageVC = - [[OversizeTextMessageViewController alloc] initWithDisplayableText:displayableText - attachmentStream:attachmentStream]; - [self.navigationController pushViewController:messageVC animated:YES]; + TSMessage *message = (TSMessage *)conversationItem.interaction; + MessageDetailViewController *view = + [[MessageDetailViewController alloc] initWithViewItem:conversationItem + message:message + mode:MessageMetadataViewModeFocusOnMessage]; + [self.navigationController pushViewController:view animated:YES]; } - (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem @@ -2074,12 +2074,17 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { [self handleUnsentMessageTap:message]; } -- (void)showMetadataViewForMessage:(TSMessage *)message +- (void)showMetadataViewForViewItem:(ConversationViewItem *)conversationItem { OWSAssert([NSThread isMainThread]); - OWSAssert(message); + OWSAssert(conversationItem); + OWSAssert([conversationItem.interaction isKindOfClass:[TSMessage class]]); - MessageMetadataViewController *view = [[MessageMetadataViewController alloc] initWithMessage:message]; + TSMessage *message = (TSMessage *)conversationItem.interaction; + MessageDetailViewController *view = + [[MessageDetailViewController alloc] initWithViewItem:conversationItem + message:message + mode:MessageMetadataViewModeFocusOnMetadata]; [self.navigationController pushViewController:view animated:YES]; } @@ -4085,7 +4090,10 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { // want to inadvertently clobber it here. OWSAssert(self.navigationController.delegate == nil) self.navigationController.delegate = self; TSMessage *message = (TSMessage *)interaction; - MessageMetadataViewController *view = [[MessageMetadataViewController alloc] initWithMessage:message]; + MessageDetailViewController *view = + [[MessageDetailViewController alloc] initWithViewItem:conversationItem + message:message + mode:MessageMetadataViewModeFocusOnMetadata]; [self.navigationController pushViewController:view animated:YES]; } else { OWSFail(@"%@ Can't show message metadata for message of type: %@", self.tag, [interaction class]); diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index b8bcd6352..3423b51c7 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -22,7 +22,10 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) { NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); +#pragma mark - + @class ConversationViewCell; +@class DisplayableText; @class OWSAudioMessageView; @class TSAttachmentPointer; @class TSAttachmentStream; @@ -72,19 +75,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); - (CGFloat)audioProgressSeconds; -#pragma mark - Expiration - -// TODO: -//@property (nonatomic, readonly) BOOL isExpiringMessage; -//@property (nonatomic, readonly) BOOL shouldStartExpireTimer; -//@property (nonatomic, readonly) double expiresAtSeconds; -//@property (nonatomic, readonly) uint32_t expiresInSeconds; - #pragma mark - View State Caching // These methods only apply to text & attachment messages. - (OWSMessageCellType)messageCellType; -- (nullable NSString *)textMessage; +- (nullable DisplayableText *)displayableText; - (nullable TSAttachmentStream *)attachmentStream; - (nullable TSAttachmentPointer *)attachmentPointer; - (CGSize)contentSize; @@ -93,13 +88,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); // if a load has previously failed. @property (nonatomic) BOOL didCellMediaFailToLoad; -// TODO: -//// Cells will request that this adapter clear its cached media views, -//// but the adapter should only honor requests from the last cell to -//// use its views. -//- (void)setLastPresentingCell:(nullable id)cell; -//- (void)clearCachedMediaViewsIfLastPresentingCell:(id)cell; - #pragma mark - UIMenuController - (NSArray *)menuControllerItems; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index 14753c245..59f9fcc01 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -37,6 +37,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } } +#pragma mark - + @interface ConversationViewItem () @property (nonatomic, nullable) NSValue *cachedCellSize; @@ -50,7 +52,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) @property (nonatomic) BOOL hasViewState; @property (nonatomic) OWSMessageCellType messageCellType; -@property (nonatomic, nullable) NSString *textMessage; +@property (nonatomic, nullable) DisplayableText *displayableText; @property (nonatomic, nullable) TSAttachmentStream *attachmentStream; @property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer; @property (nonatomic) CGSize contentSize; @@ -85,7 +87,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.hasViewState = NO; self.messageCellType = OWSMessageCellType_Unknown; - self.textMessage = nil; + self.displayableText = nil; self.attachmentStream = nil; self.attachmentPointer = nil; self.contentSize = CGSizeZero; @@ -266,47 +268,72 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return cache; } -- (NSString *)displayableTextForText:(NSString *)text interactionId:(NSString *)interactionId +- (DisplayableText *)displayableTextForText:(NSString *)text interactionId:(NSString *)interactionId { OWSAssert(text); OWSAssert(interactionId.length > 0); - NSString *_Nullable displayableText = [[self displayableTextCache] objectForKey:interactionId]; - if (!displayableText) { - // Only show up to 2kb of text. - const NSUInteger kMaxTextDisplayLength = 2 * 1024; - text = [text ows_stripped]; - displayableText = [[DisplayableTextFilter new] displayableText:text]; - if (displayableText.length > kMaxTextDisplayLength) { - // Trim whitespace before _AND_ after slicing the snipper from the string. - NSString *snippet = [ - [[displayableText ows_stripped] substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)] ows_stripped]; - displayableText = [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT", - @"A display format for oversize text messages."), - snippet]; - } - if (!displayableText) { - displayableText = @""; - } - [[self displayableTextCache] setObject:displayableText forKey:interactionId]; - } - return displayableText; + return [self displayableTextForInteractionId:interactionId + textBlock:^{ + return text; + }]; } -- (NSString *)displayableTextForAttachmentStream:(TSAttachmentStream *)attachmentStream - interactionId:(NSString *)interactionId +- (DisplayableText *)displayableTextForAttachmentStream:(TSAttachmentStream *)attachmentStream + interactionId:(NSString *)interactionId { OWSAssert(attachmentStream); OWSAssert(interactionId.length > 0); - NSString *_Nullable displayableText = [[self displayableTextCache] objectForKey:interactionId]; - if (displayableText) { - return displayableText; - } + return [self displayableTextForInteractionId:interactionId + textBlock:^{ + NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL]; + NSString *text = + [[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding]; + return text; + }]; +} - NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL]; - NSString *text = [[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding]; - return [self displayableTextForText:text interactionId:interactionId]; +- (DisplayableText *)displayableTextForInteractionId:(NSString *)interactionId + textBlock:(NSString * (^_Nonnull)())textBlock +{ + OWSAssert(interactionId.length > 0); + + DisplayableText *_Nullable displayableText = [[self displayableTextCache] objectForKey:interactionId]; + if (!displayableText) { + NSString *text = textBlock(); + + // Only show up to 2kb of text. + const NSUInteger kMaxTextDisplayLength = 2 * 1024; + text = [text ows_stripped]; + NSString *_Nullable fullText = [DisplayableText displayableText:text]; + BOOL isTextTruncated = NO; + if (!fullText) { + fullText = @""; + } else { + fullText = fullText; + } + NSString *_Nullable displayText = fullText; + if (displayText.length > kMaxTextDisplayLength) { + // Trim whitespace before _AND_ after slicing the snipper from the string. + NSString *snippet = [[displayText substringWithRange:NSMakeRange(0, kMaxTextDisplayLength)] ows_stripped]; + displayText = [NSString stringWithFormat:NSLocalizedString(@"OVERSIZE_TEXT_DISPLAY_FORMAT", + @"A display format for oversize text messages."), + snippet]; + isTextTruncated = YES; + } + if (!displayText) { + displayText = @""; + } else { + displayText = displayText; + } + + displayableText = + [[DisplayableText alloc] initWithFullText:fullText displayText:displayText isTextTruncated:isTextTruncated]; + + [[self displayableTextCache] setObject:displayableText forKey:interactionId]; + } + return displayableText; } - (void)ensureViewState @@ -321,7 +348,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) TSMessage *interaction = (TSMessage *)self.interaction; if (interaction.body != nil) { self.messageCellType = OWSMessageCellType_TextMessage; - self.textMessage = [self displayableTextForText:interaction.body interactionId:interaction.uniqueId]; + self.displayableText = [self displayableTextForText:interaction.body interactionId:interaction.uniqueId]; return; } else { NSString *_Nullable attachmentId = interaction.attachmentIds.firstObject; @@ -332,8 +359,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) if ([attachment.contentType isEqualToString:OWSMimeTypeOversizeTextMessage]) { self.messageCellType = OWSMessageCellType_OversizeTextMessage; - self.textMessage = [self displayableTextForAttachmentStream:self.attachmentStream - interactionId:interaction.uniqueId]; + self.displayableText = [self displayableTextForAttachmentStream:self.attachmentStream + interactionId:interaction.uniqueId]; return; } else if ([self.attachmentStream isAnimated] || [self.attachmentStream isImage] || [self.attachmentStream isVideo]) { @@ -387,13 +414,17 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return _messageCellType; } -- (nullable NSString *)textMessage +- (nullable DisplayableText *)displayableText { OWSAssert([NSThread isMainThread]); [self ensureViewState]; - return _textMessage; + OWSAssert(_displayableText); + OWSAssert(_displayableText.displayText); + OWSAssert(_displayableText.fullText); + + return _displayableText; } - (nullable TSAttachmentStream *)attachmentStream @@ -495,7 +526,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) switch (self.messageCellType) { case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: - [UIPasteboard.generalPasteboard setString:self.textMessage]; + OWSAssert(self.displayableText); + [UIPasteboard.generalPasteboard setString:self.displayableText.fullText]; break; case OWSMessageCellType_StillImage: case OWSMessageCellType_AnimatedImage: @@ -527,7 +559,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) switch (self.messageCellType) { case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: - [AttachmentSharing showShareUIForText:self.textMessage]; + OWSAssert(self.displayableText); + [AttachmentSharing showShareUIForText:self.displayableText.fullText]; break; case OWSMessageCellType_StillImage: case OWSMessageCellType_AnimatedImage: @@ -618,7 +651,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) switch (self.messageCellType) { case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: - return self.textMessage.length > 0; + OWSAssert(self.displayableText); + return self.displayableText.fullText.length > 0; case OWSMessageCellType_StillImage: case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: diff --git a/Signal/src/ViewControllers/CropScaleImageViewController.swift b/Signal/src/ViewControllers/CropScaleImageViewController.swift index 09c0d0166..db19bf440 100644 --- a/Signal/src/ViewControllers/CropScaleImageViewController.swift +++ b/Signal/src/ViewControllers/CropScaleImageViewController.swift @@ -101,15 +101,9 @@ class CropScaleImageViewController: OWSViewController { // MARK: Initializers - @available(*, unavailable, message:"use srcImage:successCompletion: constructor instead.") + @available(*, unavailable, message:"use other constructor instead.") required init?(coder aDecoder: NSCoder) { - self.srcImage = UIImage(named:"fail")! - self.successCompletion = { _ in - } - super.init(coder: aDecoder) - owsFail("\(self.TAG) invalid constructor") - - configureCropAndScale() + fatalError("\(#function) is unimplemented.") } required init(srcImage: UIImage, successCompletion : @escaping (UIImage) -> Void) { diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 891392d4b..3553f924c 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -627,7 +627,7 @@ NS_ASSUME_NONNULL_BEGIN @"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."]; + @"lorem, in rhoncus nisi.\n\n"]; } DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:message]; diff --git a/Signal/src/ViewControllers/GifPicker/GifPickerLayout.swift b/Signal/src/ViewControllers/GifPicker/GifPickerLayout.swift index bebfbe5ec..585df8de6 100644 --- a/Signal/src/ViewControllers/GifPicker/GifPickerLayout.swift +++ b/Signal/src/ViewControllers/GifPicker/GifPickerLayout.swift @@ -22,8 +22,7 @@ class GifPickerLayout: UICollectionViewLayout { @available(*, unavailable, message:"use other constructor instead.") required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - owsFail("\(self.TAG) invalid constructor") + fatalError("\(#function) is unimplemented.") } override init() { diff --git a/Signal/src/ViewControllers/InboxTableViewCell.m b/Signal/src/ViewControllers/InboxTableViewCell.m index 693dfa43d..615539744 100644 --- a/Signal/src/ViewControllers/InboxTableViewCell.m +++ b/Signal/src/ViewControllers/InboxTableViewCell.m @@ -188,7 +188,7 @@ const NSUInteger kAvatarViewDiameter = 52; : [UIColor lightGrayColor]), }]]; } - NSString *displayableText = [[DisplayableTextFilter new] displayableText:thread.lastMessageLabel]; + NSString *displayableText = [DisplayableText displayableText:thread.lastMessageLabel]; if (displayableText) { [snippetText appendAttributedString:[[NSAttributedString alloc] initWithString:displayableText diff --git a/Signal/src/ViewControllers/MessageMetadataViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift similarity index 80% rename from Signal/src/ViewControllers/MessageMetadataViewController.swift rename to Signal/src/ViewControllers/MessageDetailViewController.swift index ac52c911f..8e7f52a09 100644 --- a/Signal/src/ViewControllers/MessageMetadataViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -4,10 +4,16 @@ import Foundation -class MessageMetadataViewController: OWSViewController { +@objc +enum MessageMetadataViewMode: UInt { + case focusOnMessage + case focusOnMetadata +} - static let TAG = "[MessageMetadataViewController]" - let TAG = "[MessageMetadataViewController]" +class MessageDetailViewController: OWSViewController { + + static let TAG = "[MessageDetailViewController]" + let TAG = "[MessageDetailViewController]" // MARK: Properties @@ -18,6 +24,8 @@ class MessageMetadataViewController: OWSViewController { let bubbleFactory = OWSMessagesBubbleImageFactory() var bubbleView: UIView? + let mode: MessageMetadataViewMode + let viewItem: ConversationViewItem var message: TSMessage var mediaMessageView: MediaMessageView? @@ -32,18 +40,16 @@ class MessageMetadataViewController: OWSViewController { // MARK: Initializers - @available(*, unavailable, message:"use message: constructor instead.") + @available(*, unavailable, message:"use other constructor instead.") required init?(coder aDecoder: NSCoder) { - self.contactsManager = Environment.getCurrent().contactsManager - self.message = TSMessage() - self.databaseConnection = TSStorageManager.shared().newDatabaseConnection()! - super.init(coder: aDecoder) - owsFail("\(self.TAG) invalid constructor") + fatalError("\(#function) is unimplemented.") } - required init(message: TSMessage) { + required init(viewItem: ConversationViewItem, message: TSMessage, mode: MessageMetadataViewMode) { self.contactsManager = Environment.getCurrent().contactsManager + self.viewItem = viewItem self.message = message + self.mode = mode self.databaseConnection = TSStorageManager.shared().newDatabaseConnection()! super.init(nibName: nil, bundle: nil) } @@ -62,12 +68,15 @@ class MessageMetadataViewController: OWSViewController { createViews() self.view.layoutIfNeeded() - if let bubbleView = self.bubbleView { - let showAtLeast: CGFloat = 50 - let middleCenter = CGPoint(x: bubbleView.frame.origin.x + bubbleView.frame.width / 2, - y: bubbleView.frame.origin.y + bubbleView.frame.height - showAtLeast) - let offset = bubbleView.superview!.convert(middleCenter, to: scrollView) - self.scrollView!.setContentOffset(offset, animated: false) + + if mode == .focusOnMetadata { + if let bubbleView = self.bubbleView { + let showAtLeast: CGFloat = 50 + let middleCenter = CGPoint(x: bubbleView.frame.origin.x + bubbleView.frame.width / 2, + y: bubbleView.frame.origin.y + bubbleView.frame.height - showAtLeast) + let offset = bubbleView.superview!.convert(middleCenter, to: scrollView) + self.scrollView!.setContentOffset(offset, animated: false) + } } NotificationCenter.default.addObserver(self, @@ -267,76 +276,79 @@ class MessageMetadataViewController: OWSViewController { } } + private func displayableTextIfText() -> String? { + let messageCellType = viewItem.messageCellType() + guard messageCellType == .textMessage || + messageCellType == .oversizeTextMessage else { + return nil + } + guard let displayableText = viewItem.displayableText() else { + return nil + } + let messageBody = displayableText.fullText + guard messageBody.characters.count > 0 else { + return nil + } + return messageBody + } + private func contentRows() -> [UIView] { var rows = [UIView]() - if message.attachmentIds.count > 0 { + if let messageBody = displayableTextIfText() { + + self.messageBody = messageBody + + let isIncoming = self.message as? TSIncomingMessage != nil + + // UITextView can't render extremely long text due to constraints + // on the size of its backing buffer, especially when we're + // embedding it "full-size' within a UIScrollView as we do in this view. + // + // TODO: We could use CoreText instead, or we could dynamically + // manipulate the size/position of our UITextView to + // reflect scroll state. + let bodyLabel = UITextView() + bodyLabel.font = UIFont.ows_dynamicTypeBody() + bodyLabel.backgroundColor = UIColor.clear + bodyLabel.isOpaque = false + bodyLabel.isEditable = false + bodyLabel.isSelectable = true + bodyLabel.textContainerInset = UIEdgeInsets.zero + bodyLabel.contentInset = UIEdgeInsets.zero + bodyLabel.isScrollEnabled = false + bodyLabel.textColor = isIncoming ? UIColor.black : UIColor.white + bodyLabel.text = messageBody + + let bubbleImageData = isIncoming ? bubbleFactory.incoming : bubbleFactory.outgoing + + let leadingMargin: CGFloat = isIncoming ? 15 : 10 + let trailingMargin: CGFloat = isIncoming ? 10 : 15 + + let bubbleView = UIImageView(image: bubbleImageData.messageBubbleImage) + self.bubbleView = bubbleView + + bubbleView.layer.cornerRadius = 10 + bubbleView.addSubview(bodyLabel) + + bodyLabel.autoPinEdge(toSuperviewEdge: .leading, withInset: leadingMargin) + bodyLabel.autoPinEdge(toSuperviewEdge: .trailing, withInset: trailingMargin) + bodyLabel.autoPinHeightToSuperview(withMargin: 10) + + let row = UIView() + row.addSubview(bubbleView) + bubbleView.autoPinHeightToSuperview() + bubbleView.autoPinLeadingToSuperview(withMargin: 10) + bubbleView.autoPinTrailingToSuperview(withMargin: 10) + rows.append(row) + } else if message.attachmentIds.count > 0 { rows += addAttachmentRows() - } else if let messageBody = message.body { - // TODO: We should also display "oversize text messages" in a - // similar way. - if messageBody.characters.count > 0 { - self.messageBody = messageBody - - let isIncoming = self.message as? TSIncomingMessage != nil - - let bodyLabel = UILabel() - bodyLabel.textColor = isIncoming ? UIColor.black : UIColor.white - bodyLabel.font = UIFont.ows_regularFont(withSize: 16) - bodyLabel.text = messageBody - bodyLabel.numberOfLines = 0 - bodyLabel.lineBreakMode = .byWordWrapping - - let bubbleImageData = isIncoming ? bubbleFactory.incoming : bubbleFactory.outgoing - - let leadingMargin: CGFloat = isIncoming ? 15 : 10 - let trailingMargin: CGFloat = isIncoming ? 10 : 15 - - let bubbleView = UIImageView(image: bubbleImageData.messageBubbleImage) - self.bubbleView = bubbleView - - bubbleView.layer.cornerRadius = 10 - bubbleView.addSubview(bodyLabel) - - bodyLabel.autoPinEdge(toSuperviewEdge: .leading, withInset: leadingMargin) - bodyLabel.autoPinEdge(toSuperviewEdge: .trailing, withInset: trailingMargin) - bodyLabel.autoPinHeightToSuperview(withMargin: 10) - - // Try to hug content both horizontally and vertically, but *prefer* wide and short, to narrow and tall. - // While never exceeding max width, and never cropping content. - bodyLabel.setContentHuggingPriority(UILayoutPriorityDefaultLow, for: .horizontal) - bodyLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .vertical) - bodyLabel.setContentCompressionResistancePriority(UILayoutPriorityRequired, for: .vertical) - bodyLabel.autoSetDimension(.width, toSize: ScaleFromIPhone5(210), relation: .lessThanOrEqual) - - let bubbleSpacer = UIView() - - let row = UIView() - row.addSubview(bubbleView) - row.addSubview(bubbleSpacer) - - bubbleView.autoPinHeightToSuperview() - bubbleSpacer.autoPinHeightToSuperview() - bubbleSpacer.setContentHuggingLow() - - if isIncoming { - bubbleView.autoPinLeadingToSuperview(withMargin: 10) - bubbleSpacer.autoPinLeading(toTrailingOf: bubbleView) - bubbleSpacer.autoPinTrailingToSuperview(withMargin: 10) - } else { - bubbleSpacer.autoPinLeadingToSuperview(withMargin: 10) - bubbleView.autoPinLeading(toTrailingOf: bubbleSpacer) - bubbleView.autoPinTrailingToSuperview(withMargin: 10) - } - - rows.append(row) - } else { - // Neither attachment nor body. - owsFail("\(self.TAG) Message has neither attachment nor body.") - rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_NO_ATTACHMENT_OR_BODY", - comment: "Label for messages without a body or attachment in the 'message metadata' view."), - value: "")) - } + } else { + // Neither attachment nor body. + owsFail("\(self.TAG) Message has neither attachment nor body.") + rows.append(valueRow(name: NSLocalizedString("MESSAGE_METADATA_VIEW_NO_ATTACHMENT_OR_BODY", + comment: "Label for messages without a body or attachment in the 'message metadata' view."), + value: "")) } let spacer = UIView() diff --git a/Signal/src/ViewControllers/ModalActivityIndicatorViewController.swift b/Signal/src/ViewControllers/ModalActivityIndicatorViewController.swift index e66df9a6d..3398e2f35 100644 --- a/Signal/src/ViewControllers/ModalActivityIndicatorViewController.swift +++ b/Signal/src/ViewControllers/ModalActivityIndicatorViewController.swift @@ -23,11 +23,9 @@ class ModalActivityIndicatorViewController: OWSViewController { // MARK: Initializers - @available(*, unavailable, message:"use canCancel:completion: constructor instead.") + @available(*, unavailable, message:"use other constructor instead.") required init?(coder aDecoder: NSCoder) { - self.canCancel = false - super.init(coder: aDecoder) - owsFail("\(self.TAG) invalid constructor") + fatalError("\(#function) is unimplemented.") } required init(canCancel: Bool) { diff --git a/Signal/src/ViewControllers/OversizeTextMessageViewController.swift b/Signal/src/ViewControllers/OversizeTextMessageViewController.swift deleted file mode 100644 index ec4ea3387..000000000 --- a/Signal/src/ViewControllers/OversizeTextMessageViewController.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -import Foundation -import WebRTC -import PromiseKit - -class OversizeTextMessageViewController: OWSViewController { - - let TAG = "[OversizeTextMessageViewController]" - - let displayableText: String - let attachmentStream: TSAttachmentStream - - // MARK: Initializers - - @available(*, unavailable, message:"use message: constructor instead.") - required init?(coder aDecoder: NSCoder) { - displayableText = "" - attachmentStream = TSAttachmentStream(contentType:"", sourceFilename:"") - super.init(coder: aDecoder) - } - - required init(displayableText: String, attachmentStream: TSAttachmentStream) { - self.displayableText = displayableText - self.attachmentStream = attachmentStream - super.init(nibName: nil, bundle: nil) - } - - // MARK: View Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - self.navigationItem.title = NSLocalizedString("OVERSIZE_TEXT_MESSAGE_VIEW_TITLE", - comment: "The title of the 'oversize text message' view.") - - self.view.backgroundColor = UIColor.white - - let textView = UITextView() - textView.textColor = UIColor.black - textView.text = displayableText - textView.font = UIFont.ows_dynamicTypeBody() - textView.isEditable = false - textView.textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 8, right: 4) - self.view.addSubview(textView) - textView.autoPinWidthToSuperview() - textView.autoPin(toTopLayoutGuideOf : self, withInset: 0) - - let footerBar = UIToolbar() - footerBar.barTintColor = UIColor.ows_signalBrandBlue() - footerBar.setItems([ - UIBarButtonItem(barButtonSystemItem:.flexibleSpace, - target:nil, - action:nil), - UIBarButtonItem(barButtonSystemItem:.action, - target:self, - action:#selector(shareWasPressed)), - UIBarButtonItem(barButtonSystemItem:.flexibleSpace, - target:nil, - action:nil) - ], animated: false) - self.view.addSubview(footerBar) - footerBar.autoPinWidthToSuperview() - footerBar.autoPin(toBottomLayoutGuideOf : self, withInset: 0) - footerBar.autoPinEdge(.top, to:.bottom, of:textView) - } - - func shareWasPressed(sender: UIButton) { - Logger.info("\(TAG) sharing oversize text.") - - AttachmentSharing.showShareUI(for:attachmentStream.mediaURL()) - } -} diff --git a/Signal/src/util/DisplayableTextFilter.swift b/Signal/src/util/DisplayableText.swift similarity index 57% rename from Signal/src/util/DisplayableTextFilter.swift rename to Signal/src/util/DisplayableText.swift index ab2258fa6..11daa20e0 100644 --- a/Signal/src/util/DisplayableTextFilter.swift +++ b/Signal/src/util/DisplayableText.swift @@ -4,12 +4,26 @@ import Foundation -@objc class DisplayableTextFilter: NSObject { +@objc class DisplayableText: NSObject { - let TAG = "[DisplayableTextFilter]" + static let TAG = "[DisplayableText]" + + let fullText: String + let displayText: String + let isTextTruncated: Bool + + // MARK: Initializers + + init(fullText: String, displayText: String, isTextTruncated: Bool) { + self.fullText = fullText + self.displayText = displayText + self.isTextTruncated = isTextTruncated + } + + // MARK: Filter Methods @objc - func displayableText(_ text: String?) -> String? { + class func displayableText(_ text: String?) -> String? { guard let text = text else { return nil } @@ -17,13 +31,13 @@ import Foundation if (self.hasExcessiveDiacriticals(text: text)) { Logger.warn("\(TAG) filtering text for excessive diacriticals.") let filteredText = text.folding(options: .diacriticInsensitive, locale: .current) - return filteredText + return filteredText.ows_stripped() } - return text + return text.ows_stripped() } - private func hasExcessiveDiacriticals(text: String) -> Bool { + private class func hasExcessiveDiacriticals(text: String) -> Bool { // discard any zalgo style text, by detecting maximum number of glyphs per character for char in text.characters.enumerated() { let scalarCount = String(char.element).unicodeScalars.count diff --git a/Signal/src/views/AudioProgressView.swift b/Signal/src/views/AudioProgressView.swift index 1aef8f365..5e0d41749 100644 --- a/Signal/src/views/AudioProgressView.swift +++ b/Signal/src/views/AudioProgressView.swift @@ -45,14 +45,9 @@ import UIKit } } - @available(*, unavailable, message:"use init() constructor instead.") + @available(*, unavailable, message:"use other constructor instead.") required init?(coder aDecoder: NSCoder) { - self.horizontalBarLayer = CAShapeLayer() - self.progressLayer = CAShapeLayer() - - super.init(coder: aDecoder) - - owsFail("\(self.tag) Invalid constructor") + fatalError("\(#function) is unimplemented.") } public required init() { diff --git a/Signal/src/views/OWSFlatButton.swift b/Signal/src/views/OWSFlatButton.swift index 27fd169d7..8fdfff34f 100644 --- a/Signal/src/views/OWSFlatButton.swift +++ b/Signal/src/views/OWSFlatButton.swift @@ -30,13 +30,9 @@ import Foundation createContent() } - @available(*, unavailable, message:"use default constructor instead.") + @available(*, unavailable, message:"use other constructor instead.") required init?(coder aDecoder: NSCoder) { - button = UIButton(type:.custom) - - super.init(coder: aDecoder) - - owsFail("\(self.TAG) invalid constructor") + fatalError("\(#function) is unimplemented.") } private func createContent() { diff --git a/Signal/test/util/DisplayableTextFilterTest.swift b/Signal/test/util/DisplayableTextFilterTest.swift index 5a94660bb..39dd329ac 100644 --- a/Signal/test/util/DisplayableTextFilterTest.swift +++ b/Signal/test/util/DisplayableTextFilterTest.swift @@ -4,13 +4,13 @@ import XCTest -class DisplayableTextFilterTest: XCTestCase { - +class DisplayableTextTest: XCTestCase { + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() @@ -18,7 +18,7 @@ class DisplayableTextFilterTest: XCTestCase { func testDisplayableText() { // Ignore default byte size limitations to test other filtering behaviors - let filter = DisplayableTextFilter() + let filter = DisplayableText() // show plain text let boringText = "boring text" diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 6feb95e68..6da470e39 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -76,18 +76,12 @@ /* No comment provided by engineer. */ "ATTACHMENT" = "Attachment"; -/* Title for the 'attachment approval' dialog. */ -"ATTACHMENT_APPROVAL_DIALOG_TITLE" = "Attachment"; - /* Format string for file extension label in call interstitial view */ "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT" = "File type: %@"; /* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Size: %@"; -/* Label for 'send' button in the 'attachment approval' dialog. */ -"ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send"; - /* Generic filename for an attachment with no known name */ "ATTACHMENT_DEFAULT_FILENAME" = "Attachment"; @@ -238,6 +232,9 @@ /* Accessibility label for hang up call */ "CALL_VIEW_HANGUP_LABEL" = "End call"; +/* Accessibility label for muting the microphone */ +"CALL_VIEW_MUTE_LABEL" = "Mute"; + /* Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy. */ "CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL" = "You can answer calls directly from your lock screen and see the name and phone number for incoming calls if you change your settings.\n\nSee the privacy settings for details."; @@ -256,9 +253,6 @@ /* Accessibility label to switch to video call */ "CALL_VIEW_SWITCH_TO_VIDEO_LABEL" = "Switch to video call"; -/* Accessibility label for muting the microphone */ -"CALL_VIEW_MUTE_LABEL" = "Mute"; - /* notification action */ "CALLBACK_BUTTON_TITLE" = "Call Back"; @@ -400,6 +394,9 @@ /* Title for the group of buttons show for unknown contacts offering to add them to contacts, etc. */ "CONVERSATION_VIEW_CONTACTS_OFFER_TITLE" = "This user is not in your contacts."; +/* Indicator on truncated text messages that they can be tapped to see the entire text message. */ +"CONVERSATION_VIEW_OVERSIZE_TEXT_TAP_FOR_MORE" = "Tap For More"; + /* Message shown in conversation view that offers to block an unknown user. */ "CONVERSATION_VIEW_UNKNOWN_CONTACT_BLOCK_OFFER" = "Block This User"; @@ -818,10 +815,10 @@ "LIST_GROUP_MEMBERS_ACTION" = "Group Members"; /* No comment provided by engineer. */ -"LOGGING_SECTION" = "Logging"; +"load_earlier_messages" = "Load Earlier Messages"; /* No comment provided by engineer. */ -"ME_STRING" = "Me"; +"LOGGING_SECTION" = "Logging"; /* media picker option to take photo or video */ "MEDIA_FROM_CAMERA_BUTTON" = "Camera"; @@ -883,8 +880,7 @@ /* Title for the 'message metadata' view. */ "MESSAGE_METADATA_VIEW_TITLE" = "Message"; -/* message footer for delivered messages - message status for message delivered to their recipient. */ +/* message status for message delivered to their recipient. */ "MESSAGE_STATUS_DELIVERED" = "Delivered"; /* message footer for failed messages */ @@ -958,24 +954,9 @@ Alert title when camera is not authorized */ "MISSING_CAMERA_PERMISSION_TITLE" = "Signal needs to access your camera."; -/* No comment provided by engineer. */ -"MSGVIEW_MISSED_CALL_BECAUSE_OF_CHANGED_IDENTITY" = "Missed call because their safety number has changed."; - /* notification title. Embeds {{caller's name or phone number}} */ "MSGVIEW_MISSED_CALL_WITH_NAME" = "Missed call from %@."; -/* No comment provided by engineer. */ -"MSGVIEW_RECEIVED_CALL" = "You received a call from %@."; - -/* No comment provided by engineer. */ -"MSGVIEW_THEY_TRIED_TO_CALL_YOU" = "%@ tried to call you."; - -/* No comment provided by engineer. */ -"MSGVIEW_YOU_CALLED" = "You called %@."; - -/* No comment provided by engineer. */ -"MSGVIEW_YOU_TRIED_TO_CALL" = "You tried to call %@."; - /* No comment provided by engineer. */ "MULTIDEVICE_PAIRING_MAX_DESC" = "You can not pair any more devices."; @@ -1036,6 +1017,9 @@ /* The alert title if user tries to exit the new group view without saving changes. */ "NEW_GROUP_VIEW_UNSAVED_CHANGES_TITLE" = "Unsaved Changes"; +/* No comment provided by engineer. */ +"new_message" = "New Message"; + /* A label for the 'add by phone number' button in the 'new non-contact conversation' view */ "NEW_NONCONTACT_CONVERSATION_VIEW_BUTTON" = "Search"; @@ -1092,7 +1076,7 @@ "OUTGOING_INCOMPLETE_CALL" = "Unanswered outgoing call"; /* A display format for oversize text messages. */ -"OVERSIZE_TEXT_DISPLAY_FORMAT" = "%@… [Tap For More]"; +"OVERSIZE_TEXT_DISPLAY_FORMAT" = "%@…"; /* The title of the 'oversize text message' view. */ "OVERSIZE_TEXT_MESSAGE_VIEW_TITLE" = "Message";