diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c0888091c..3f418be51 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 */; }; @@ -477,7 +476,6 @@ 34B3F8571E8DF1700035BE1A /* NotificationSettingsOptionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsOptionsViewController.m; sourceTree = "<group>"; }; 34B3F8581E8DF1700035BE1A /* NotificationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationSettingsViewController.h; sourceTree = "<group>"; }; 34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationSettingsViewController.m; sourceTree = "<group>"; }; - 34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OversizeTextMessageViewController.swift; sourceTree = "<group>"; }; 34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewController.h; sourceTree = "<group>"; }; 34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSConversationSettingsViewController.m; sourceTree = "<group>"; }; 34B3F85D1E8DF1700035BE1A /* OWSLinkDeviceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkDeviceViewController.h; sourceTree = "<group>"; }; @@ -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 */, @@ -2291,7 +2288,6 @@ 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 */, 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..0d9a8a5c3 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2040,18 +2040,17 @@ 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); - // 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; + MessageMetadataViewController *view = + [[MessageMetadataViewController alloc] initWithViewItem:conversationItem + message:message + mode:MessageMetadataViewModeFocusOnMessage]; + [self.navigationController pushViewController:view animated:YES]; } - (void)didTapFailedIncomingAttachment:(ConversationViewItem *)viewItem @@ -2074,12 +2073,16 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { [self handleUnsentMessageTap:message]; } -- (void)showMetadataViewForMessage:(TSMessage *)message +- (void)showMetadataViewForViewItem:(ConversationViewItem *)conversationItem { OWSAssert([NSThread isMainThread]); - OWSAssert(message); + OWSAssert(conversationItem); - MessageMetadataViewController *view = [[MessageMetadataViewController alloc] initWithMessage:message]; + TSMessage *message = (TSMessage *)conversationItem.interaction; + MessageMetadataViewController *view = + [[MessageMetadataViewController alloc] initWithViewItem:conversationItem + message:message + mode:MessageMetadataViewModeFocusOnMetadata]; [self.navigationController pushViewController:view animated:YES]; } @@ -4085,7 +4088,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]; + MessageMetadataViewController *view = + [[MessageMetadataViewController 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..45ed5c93d 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -22,6 +22,20 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) { NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); +#pragma mark - + +@interface DisplayableText : NSObject + +@property (nonatomic) NSString *fullText; + +@property (nonatomic) NSString *displayText; + +@property (nonatomic) BOOL isTextTruncated; + +@end + +#pragma mark - + @class ConversationViewCell; @class OWSAudioMessageView; @class TSAttachmentPointer; @@ -72,19 +86,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 +99,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<UIMenuItem *> *)menuControllerItems; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index 14753c245..58a5a385a 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -37,6 +37,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } } +#pragma mark - + +@implementation DisplayableText + +@end + +#pragma mark - + @interface ConversationViewItem () @property (nonatomic, nullable) NSValue *cachedCellSize; @@ -50,7 +58,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 +93,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,49 +274,71 @@ 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]; + return [self displayableTextForInteractionId:interactionId + textBlock:^{ + return text; + }]; +} + +- (DisplayableText *)displayableTextForAttachmentStream:(TSAttachmentStream *)attachmentStream + interactionId:(NSString *)interactionId +{ + OWSAssert(attachmentStream); + OWSAssert(interactionId.length > 0); + + return [self displayableTextForInteractionId:interactionId + textBlock:^{ + NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL]; + NSString *text = + [[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding]; + return text; + }]; +} + +- (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]; - displayableText = [[DisplayableTextFilter new] displayableText:text]; - if (displayableText.length > kMaxTextDisplayLength) { + NSString *fullText = [[[DisplayableTextFilter new] displayableText:text] ows_stripped]; + displayableText = [DisplayableText new]; + if (!fullText) { + displayableText.fullText = @""; + } else { + displayableText.fullText = fullText; + } + NSString *displayText = fullText; + if (displayText.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]; + 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]; + displayableText.isTextTruncated = YES; } - if (!displayableText) { - displayableText = @""; + if (!displayText) { + displayableText.displayText = @""; + } else { + displayableText.displayText = displayText; } + [[self displayableTextCache] setObject:displayableText forKey:interactionId]; } return displayableText; } -- (NSString *)displayableTextForAttachmentStream:(TSAttachmentStream *)attachmentStream - interactionId:(NSString *)interactionId -{ - OWSAssert(attachmentStream); - OWSAssert(interactionId.length > 0); - - NSString *_Nullable displayableText = [[self displayableTextCache] objectForKey:interactionId]; - if (displayableText) { - return displayableText; - } - - NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL]; - NSString *text = [[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding]; - return [self displayableTextForText:text interactionId:interactionId]; -} - - (void)ensureViewState { OWSAssert([self.interaction isKindOfClass:[TSMessage class]]); @@ -321,7 +351,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 +362,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 +417,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 +529,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 +562,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 +654,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/MessageMetadataViewController.swift b/Signal/src/ViewControllers/MessageMetadataViewController.swift index ac52c911f..fd9334ae5 100644 --- a/Signal/src/ViewControllers/MessageMetadataViewController.swift +++ b/Signal/src/ViewControllers/MessageMetadataViewController.swift @@ -4,6 +4,12 @@ import Foundation +@objc +enum MessageMetadataViewMode: UInt { + case focusOnMessage + case focusOnMetadata +} + class MessageMetadataViewController: OWSViewController { static let TAG = "[MessageMetadataViewController]" @@ -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/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/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";