Rework handling of oversize text messages.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-10-25 14:53:54 -04:00
parent 97bab48a93
commit bcf83a4c8e
15 changed files with 280 additions and 302 deletions

View File

@ -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 */,

View File

@ -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

View File

@ -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

View File

@ -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]);

View File

@ -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;

View File

@ -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,47 +274,69 @@ 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 *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 = [[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 (!displayText) {
displayableText.displayText = @"";
} else {
displayableText.displayText = displayText;
}
[[self displayableTextCache] setObject:displayableText forKey:interactionId];
}
return displayableText;
}
- (void)ensureViewState
@ -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:

View File

@ -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) {

View File

@ -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];

View File

@ -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() {

View File

@ -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()

View File

@ -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) {

View File

@ -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())
}
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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";