| 
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@property (nonatomic) OWSBubbleShapeView *mediaClipView;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@property (nonatomic) OWSBubbleShapeView *bubbleStrokeView;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@property (nonatomic) UIStackView *stackView;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@property (nonatomic) UILabel *senderNameLabel;
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -81,10 +83,10 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.mediaShadowView = [OWSBubbleShapeView bubbleShadowView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.mediaClipView = [OWSBubbleShapeView bubbleClipView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.bubbleStrokeView = [OWSBubbleShapeView bubbleDrawView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.stackView = [UIStackView new];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.stackView.axis = UILayoutConstraintAxisVertical;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.stackView.alignment = UIStackViewAlignmentFill;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    self.senderNameLabel = [UILabel new];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -246,10 +248,10 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.viewItem.interaction);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize quotedMessageContentSize = [self quotedMessageSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSValue *_Nullable quotedMessageSize = [self quotedMessageSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // TODO:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize bodyMediaContentSize = [self bodyMediaSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize bodyTextContentSize = [self bodyTextSizeWithIncludeMargins:NO];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //    CGSize bodyMediaContentSize = [self bodyMediaSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSValue *_Nullable bodyTextSize = [self bodyTextSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [self.bubbleView addSubview:self.stackView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [self.viewConstraints addObjectsFromArray:[self.stackView autoPinEdgesToSuperviewEdges]];
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -278,14 +280,14 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.quotedMessageView = quotedMessageView;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [quotedMessageView createContents];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self.stackView addArrangedSubview:quotedMessageView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self.viewConstraints
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            addObject:[quotedMessageView autoSetDimension:ALDimensionHeight toSize:quotedMessageContentSize.height]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        OWSAssert(quotedMessageSize);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self.viewConstraints addObject:[quotedMessageView autoSetDimension:ALDimensionHeight
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                     toSize:quotedMessageSize.CGSizeValue.height]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self.bubbleView addPartnerView:quotedMessageView.boundsStrokeView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    UIView *_Nullable bodyMediaView = nil;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL hasThumbnailForBodyMedia = NO;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    switch (self.cellType) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Unknown:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_TextMessage:
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -294,17 +296,14 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_StillImage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            OWSAssert(self.viewItem.attachmentStream);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyMediaView = [self loadViewForStillImage];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            hasThumbnailForBodyMedia = YES;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_AnimatedImage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            OWSAssert(self.viewItem.attachmentStream);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyMediaView = [self loadViewForAnimatedImage];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            hasThumbnailForBodyMedia = YES;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Video:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            OWSAssert(self.viewItem.attachmentStream);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyMediaView = [self loadViewForVideo];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            hasThumbnailForBodyMedia = YES;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Audio:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            OWSAssert(self.viewItem.attachmentStream);
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -325,51 +324,66 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        OWSAssert(self.loadCellContentBlock);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        OWSAssert(self.unloadCellContentBlock);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Flush any pending "text" subviews.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self insertAnyTextViewsIntoStackView:textViews];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [textViews removeAllObjects];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        bodyMediaView.clipsToBounds = YES;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.bodyMediaView = bodyMediaView;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        bodyMediaView.userInteractionEnabled = NO;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (self.isMediaBeingSent) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // TODO:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyMediaView.layer.opacity = 0.75f;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (hasThumbnailForBodyMedia) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // The "body media" view casts a shadow "downward" onto adjacent views,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // so we use a "proxy" view to take its place within the v-stack
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // view and then insert the body media view above its proxy so that
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // it floats above the other content of the bubble view.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            UIView *bodyProxyView = [UIView new];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.stackView addArrangedSubview:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self addSubview:self.mediaShadowView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaShadowView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaShadowView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaShadowView autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaShadowView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaShadowView addSubview:self.mediaClipView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaClipView autoPinToSuperviewEdges];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.mediaClipView addSubview:bodyMediaView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [bodyMediaView autoPinToSuperviewEdges];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.bubbleView addPartnerView:self.mediaClipView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.bubbleView addPartnerView:self.mediaShadowView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // TODO: Constants
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // TODO: What's the difference between an inner and outer shadow?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.mediaShadowView.fillColor = self.bubbleColor;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.mediaShadowView.layer.shadowColor = [UIColor blackColor].CGColor;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.mediaShadowView.layer.shadowOpacity = 0.12f;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.mediaShadowView.layer.shadowOffset = CGSizeMake(0.f, 0.f);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.mediaShadowView.layer.shadowRadius = 0.5f;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (self.hasFullWidthMediaView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Flush any pending "text" subviews.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self insertAnyTextViewsIntoStackView:textViews];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [textViews removeAllObjects];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (self.hasBodyMediaWithThumbnail) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // The "body media" view casts a shadow "downward" onto adjacent views,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // so we use a "proxy" view to take its place within the v-stack
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // view and then insert the body media view above its proxy so that
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // it floats above the other content of the bubble view.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                UIView *bodyProxyView = [UIView new];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.stackView addArrangedSubview:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self addSubview:self.mediaShadowView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaShadowView autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaShadowView autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaShadowView autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaShadowView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:bodyProxyView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaShadowView addSubview:self.mediaClipView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaClipView autoPinToSuperviewEdges];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.mediaClipView addSubview:bodyMediaView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [bodyMediaView autoPinToSuperviewEdges];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.bubbleView addPartnerView:self.mediaClipView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.bubbleView addPartnerView:self.mediaShadowView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // TODO: Constants
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // TODO: What's the difference between an inner and outer shadow?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.mediaShadowView.fillColor = self.bubbleColor;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.mediaShadowView.layer.shadowColor = [UIColor blackColor].CGColor;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.mediaShadowView.layer.shadowOpacity = 0.12f;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.mediaShadowView.layer.shadowOffset = CGSizeMake(0.f, 0.f);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.mediaShadowView.layer.shadowRadius = 0.5f;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                OWSAssert(self.cellType == OWSMessageCellType_ContactShare);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.stackView addArrangedSubview:bodyMediaView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // TODO: Constants.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.bubbleStrokeView.strokeColor = [UIColor lightGrayColor];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.bubbleStrokeView.strokeThickness = 1.f;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.bubbleView addSubview:self.bubbleStrokeView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.bubbleStrokeView autoPinToSuperviewEdges];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                [self.bubbleView addPartnerView:self.bubbleStrokeView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.stackView addArrangedSubview:bodyMediaView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [textViews addObject:bodyMediaView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -379,8 +393,10 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self configureBodyTextView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [textViews addObject:self.bodyTextView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // TODO: Media?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        OWSAssert(bodyTextSize);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [self.viewConstraints addObjectsFromArray:@[
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.bodyTextView autoSetDimension:ALDimensionHeight toSize:bodyTextContentSize.height],
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self.bodyTextView autoSetDimension:ALDimensionHeight toSize:bodyTextSize.CGSizeValue.height],
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        UIView *_Nullable tapForMoreLabel = [self createTapForMoreLabelIfNecessary];
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -421,29 +437,12 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                             ]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                         }];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [self updateBubbleColorWithBodyMediaView:bodyMediaView];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [self updateBubbleColor];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (void)updateBubbleColorWithBodyMediaView:(nullable UIView *)bodyMediaView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (void)updateBubbleColor
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL hasOnlyBodyMediaView = NO;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    switch (self.cellType) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Unknown:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_TextMessage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_OversizeTextMessage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_GenericAttachment:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_DownloadingAttachment:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_ContactShare:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_StillImage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_AnimatedImage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Audio:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Video:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            hasOnlyBodyMediaView = (bodyMediaView && self.stackView.subviews.count == 1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL hasOnlyBodyMediaView = (self.hasBodyMediaWithThumbnail && self.stackView.subviews.count == 1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (!hasOnlyBodyMediaView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.bubbleView.bubbleColor = self.bubbleColor;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    } else {
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -461,7 +460,7 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [self.bubbleFactory bubbleColorWithMessage:message];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (BOOL)canFooterOverlayMedia
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (BOOL)hasBodyMediaWithThumbnail
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    switch (self.cellType) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Unknown:
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -480,6 +479,22 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (BOOL)hasFullWidthMediaView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return (self.hasBodyMediaWithThumbnail || self.cellType == OWSMessageCellType_ContactShare);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (BOOL)canFooterOverlayMedia
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return self.hasBodyMediaWithThumbnail;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (BOOL)hasBottomFooter
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL shouldFooterOverlayMedia = (self.canFooterOverlayMedia && !self.hasBodyText);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return !self.viewItem.shouldHideFooter && !shouldFooterOverlayMedia;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (void)insertAnyTextViewsIntoStackView:(NSArray<UIView *> *)textViews
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (textViews.count < 1) {
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -488,7 +503,6 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    UIStackView *textStackView = [[UIStackView alloc] initWithArrangedSubviews:textViews];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    textStackView.axis = UILayoutConstraintAxisVertical;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    textStackView.alignment = UIStackViewAlignmentFill;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // TODO: Review
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    textStackView.spacing = self.textViewVSpacing;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    textStackView.layoutMarginsRelativeArrangement = YES;
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -930,13 +944,13 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#pragma mark - Measurement
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// Size of "message body" text, not quoted reply text.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (CGSize)bodyTextSizeWithIncludeMargins:(BOOL)includeMargins
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (nullable NSValue *)bodyTextSize
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle.maxMessageWidth > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (!self.hasBodyText) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return CGSizeZero;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return nil;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGFloat hMargins = self.conversationStyle.textInsetHorizontal * 2;
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -947,15 +961,10 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const int kMaxIterations = 5;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize result = [self.bodyTextView compactSizeThatFitsMaxWidth:maxTextWidth maxIterations:kMaxIterations];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (includeMargins) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        result.width += hMargins;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        result.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return CGSizeCeil(result);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [NSValue valueWithCGSize:CGSizeCeil(result)];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (CGSize)bodyMediaSize
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (nullable NSValue *)bodyMediaSize
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle.maxMessageWidth > 0);
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -967,8 +976,7 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_Unknown:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_TextMessage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_OversizeTextMessage: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            result = CGSizeZero;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return nil;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_StillImage:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        case OWSMessageCellType_AnimatedImage:
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -1024,10 +1032,10 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            break;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return CGSizeCeil(result);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [NSValue valueWithCGSize:CGSizeCeil(result)];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (CGSize)quotedMessageSize
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (nullable NSValue *)quotedMessageSize
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle.maxMessageWidth > 0);
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -1035,7 +1043,7 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (!self.isQuotedReply) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return CGSizeZero;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return nil;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class];
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -1047,16 +1055,16 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                         displayableQuotedText:displayableQuotedText
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                    isOutgoing:isOutgoing];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize result = [quotedMessageView sizeForMaxWidth:self.conversationStyle.maxMessageWidth];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return CGSizeCeil(result);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [NSValue valueWithCGSize:CGSizeCeil(result)];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (CGSize)senderNameSizeWithBodyMediaSize:(CGSize)bodyMediaSize includeMargins:(BOOL)includeMargins
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (nullable NSValue *)senderNameSize
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle.maxMessageWidth > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (!self.shouldShowSenderName) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return CGSizeZero;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return nil;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGFloat hMargins = self.conversationStyle.textInsetHorizontal * 2;
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -1064,18 +1072,7 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [self configureSenderNameLabel];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize result = CGSizeCeil([self.senderNameLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL hasSeparateTextStackView = (self.isQuotedReply || bodyMediaSize.width > 0 || bodyMediaSize.height > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (includeMargins) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        result.width += hMargins;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (hasSeparateTextStackView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            result.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            result.height += self.textViewVSpacing;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return result;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [NSValue valueWithCGSize:result];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (CGSize)measureSize
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -1087,24 +1084,54 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize cellSize = CGSizeZero;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // TODO: Reflect "sender name" and "footer" layout.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //    shouldFooterOverlayMedia = self.canFooterOverlayMedia;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSMutableArray<NSValue *> *textViewSizes = [NSMutableArray new];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize quotedMessageSize = [self quotedMessageSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.width = MAX(cellSize.width, quotedMessageSize.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.height += quotedMessageSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSValue *_Nullable senderNameSize = [self senderNameSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (senderNameSize) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [textViewSizes addObject:senderNameSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize bodyMediaSize = [self bodyMediaSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.width = MAX(cellSize.width, bodyMediaSize.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.height += bodyMediaSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSValue *_Nullable quotedMessageSize = [self quotedMessageSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (quotedMessageSize) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.width = MAX(cellSize.width, quotedMessageSize.CGSizeValue.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.height += quotedMessageSize.CGSizeValue.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize senderNameSize = [self senderNameSizeWithBodyMediaSize:bodyMediaSize includeMargins:YES];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.width = MAX(cellSize.width, senderNameSize.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.height += senderNameSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSValue *_Nullable bodyMediaSize = [self bodyMediaSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (bodyMediaSize) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (self.hasFullWidthMediaView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            cellSize.width = MAX(cellSize.width, bodyMediaSize.CGSizeValue.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            cellSize.height += bodyMediaSize.CGSizeValue.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [textViewSizes addObject:bodyMediaSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyMediaSize = nil;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize textContentSize = [self bodyTextSizeWithIncludeMargins:YES];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.width = MAX(cellSize.width, textContentSize.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.height += textContentSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (bodyMediaSize || quotedMessageSize) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (textViewSizes.count > 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            CGSize groupSize = [self sizeForTextViewGroup:textViewSizes];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            cellSize.width = MAX(cellSize.width, groupSize.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            cellSize.height += groupSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [textViewSizes removeAllObjects];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSValue *_Nullable bodyTextSize = [self bodyTextSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (bodyTextSize) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [textViewSizes addObject:bodyTextSize];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (self.hasBottomFooter) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [textViewSizes addObject:[NSValue valueWithCGSize:footerSize]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (textViewSizes.count > 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        CGSize groupSize = [self sizeForTextViewGroup:textViewSizes];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.width = MAX(cellSize.width, groupSize.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.height += groupSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // Make sure the bubble is always wide enough to complete it's bubble shape.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize.width = MAX(cellSize.width, OWSBubbleView.minWidth);
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -1115,23 +1142,30 @@ NS_ASSUME_NONNULL_BEGIN
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.height += self.tapForMoreHeight + self.textViewVSpacing;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // TODO: Update this to reflect generic attachment, downloading attachments and
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //       contact shares.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    BOOL shouldFooterOverlayMedia = (self.canFooterOverlayMedia && !self.hasBodyText);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (!self.viewItem.shouldHideFooter && !shouldFooterOverlayMedia) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.width = MAX(cellSize.width, footerSize.width + self.conversationStyle.textInsetHorizontal * 2);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        cellSize.height += self.textViewVSpacing + footerSize.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (!self.hasBodyText) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            cellSize.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    cellSize = CGSizeCeil(cellSize);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return cellSize;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (CGSize)sizeForTextViewGroup:(NSArray<NSValue *> *)textViewSizes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(textViewSizes);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(textViewSizes.count > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(self.conversationStyle.maxMessageWidth > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    CGSize result = CGSizeZero;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    for (NSValue *size in textViewSizes) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        result.width = MAX(result.width, size.CGSizeValue.width);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        result.height += size.CGSizeValue.height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    result.height += self.textViewVSpacing * (textViewSizes.count - 1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    result.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    result.width += self.conversationStyle.textInsetHorizontal * 2;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return result;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (UIFont *)tapForMoreFont
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return UIFont.ows_dynamicTypeCaption1Font;
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |