|  |  | @ -69,6 +69,8 @@ NS_ASSUME_NONNULL_BEGIN | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | @interface OWSMessageTextView : UITextView |  |  |  | @interface OWSMessageTextView : UITextView | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | @property (nonatomic) BOOL shouldIgnoreEvents; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | @end |  |  |  | @end | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | #pragma mark - |  |  |  | #pragma mark - | 
			
		
	
	
		
		
			
				
					|  |  | @ -83,6 +85,50 @@ NS_ASSUME_NONNULL_BEGIN | 
			
		
	
		
		
			
				
					
					|  |  |  |     return NO; |  |  |  |     return NO; | 
			
		
	
		
		
			
				
					
					|  |  |  | } |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // Ignore interactions with the text view _except_ taps on links. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // We want to disable "partial" selection of text in the message | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | // and we want to enable "tap to resend" by tapping on a message. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *_Nullable)event | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     if (self.shouldIgnoreEvents) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         // We ignore all events for failed messages so that users | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         // can tap-to-resend even "all link" messages. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         return NO; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     // Find the nearest text position to the event. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     UITextPosition *_Nullable position = [self closestPositionToPoint:point]; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     if (!position) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         return NO; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     // Find the range of the character in the text which contains the event. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     // | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     // Try every layout direction (this might not be necessary). | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     UITextRange *_Nullable range = nil; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     for (NSNumber *textLayoutDirection in @[ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |              @(UITextLayoutDirectionLeft), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |              @(UITextLayoutDirectionRight), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |              @(UITextLayoutDirectionUp), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |              @(UITextLayoutDirectionDown), | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |          ]) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         range = [self.tokenizer rangeEnclosingPosition:position | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                        withGranularity:UITextGranularityCharacter | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                            inDirection:(UITextDirection)textLayoutDirection.intValue]; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         if (range) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |             break; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     if (!range) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         return NO; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     // Ignore the event unless it occurred inside a link. | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument toPosition:range.start]; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     BOOL result = | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         [self.attributedText attribute:NSLinkAttributeName atIndex:(NSUInteger)startIndex effectiveRange:nil] != nil; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     return result; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | @end |  |  |  | @end | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | #pragma mark - |  |  |  | #pragma mark - | 
			
		
	
	
		
		
			
				
					|  |  | @ -94,7 +140,7 @@ NS_ASSUME_NONNULL_BEGIN | 
			
		
	
		
		
			
				
					
					|  |  |  | // to always keep one around. |  |  |  | // to always keep one around. | 
			
		
	
		
		
			
				
					
					|  |  |  | @property (nonatomic) BubbleMaskingView *payloadView; |  |  |  | @property (nonatomic) BubbleMaskingView *payloadView; | 
			
		
	
		
		
			
				
					
					|  |  |  | @property (nonatomic) UILabel *dateHeaderLabel; |  |  |  | @property (nonatomic) UILabel *dateHeaderLabel; | 
			
		
	
		
		
			
				
					
					|  |  |  | @property (nonatomic) UITextView *textView; |  |  |  | @property (nonatomic) OWSMessageTextView *textView; | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | @property (nonatomic, nullable) UIImageView *failedSendBadgeView; |  |  |  | @property (nonatomic, nullable) UIImageView *failedSendBadgeView; | 
			
		
	
		
		
			
				
					
					|  |  |  | @property (nonatomic, nullable) UILabel *tapForMoreLabel; |  |  |  | @property (nonatomic, nullable) UILabel *tapForMoreLabel; | 
			
		
	
		
		
			
				
					
					|  |  |  | @property (nonatomic, nullable) UIImageView *bubbleImageView; |  |  |  | @property (nonatomic, nullable) UIImageView *bubbleImageView; | 
			
		
	
	
		
		
			
				
					|  |  | @ -669,21 +715,20 @@ NS_ASSUME_NONNULL_BEGIN | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.textView.textColor = textColor; |  |  |  |     self.textView.textColor = textColor; | 
			
		
	
		
		
			
				
					
					|  |  |  |     // Honor dynamic type in the message bodies. |  |  |  |     // Honor dynamic type in the message bodies. | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.textView.font = [self textMessageFont]; |  |  |  |     self.textView.font = [self textMessageFont]; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     // Don't link outgoing messages that haven't been sent yet, as |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     // this interferes with "tap to retry". |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     BOOL canLinkify = YES; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         canLinkify = outgoingMessage.messageState == TSOutgoingMessageStateSentToService; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     if (canLinkify) { |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.textView.linkTextAttributes = @{ |  |  |  |     self.textView.linkTextAttributes = @{ | 
			
		
	
		
		
			
				
					
					|  |  |  |         NSForegroundColorAttributeName : textColor, |  |  |  |         NSForegroundColorAttributeName : textColor, | 
			
		
	
		
		
			
				
					
					|  |  |  |         NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) |  |  |  |         NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) | 
			
		
	
		
		
			
				
					
					|  |  |  |     }; |  |  |  |     }; | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.textView.dataDetectorTypes |  |  |  |     self.textView.dataDetectorTypes | 
			
		
	
		
		
			
				
					
					|  |  |  |         = (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent); |  |  |  |         = (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         // Ignore taps on links in outgoing messages that haven't been sent yet, as | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         // this interferes with "tap to retry". | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         self.textView.shouldIgnoreEvents = outgoingMessage.messageState != TSOutgoingMessageStateSentToService; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         self.textView.shouldIgnoreEvents = NO; | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     if (self.displayableText.isTextTruncated) { |  |  |  |     if (self.displayableText.isTextTruncated) { | 
			
		
	
	
		
		
			
				
					|  |  | 
 |