|
|
@ -25,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic) UIStackView *stackView;
|
|
|
|
@property (nonatomic) UIStackView *stackView;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic) UILabel *senderNameLabel;
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic) OWSMessageTextView *bodyTextView;
|
|
|
|
@property (nonatomic) OWSMessageTextView *bodyTextView;
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, nullable) UIView *quotedMessageView;
|
|
|
|
@property (nonatomic, nullable) UIView *quotedMessageView;
|
|
|
@ -77,6 +79,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
self.stackView.axis = UILayoutConstraintAxisVertical;
|
|
|
|
self.stackView.axis = UILayoutConstraintAxisVertical;
|
|
|
|
self.stackView.alignment = UIStackViewAlignmentFill;
|
|
|
|
self.stackView.alignment = UIStackViewAlignmentFill;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.senderNameLabel = [UILabel new];
|
|
|
|
|
|
|
|
|
|
|
|
self.bodyTextView = [self newTextView];
|
|
|
|
self.bodyTextView = [self newTextView];
|
|
|
|
// Setting dataDetectorTypes is expensive. Do it just once.
|
|
|
|
// Setting dataDetectorTypes is expensive. Do it just once.
|
|
|
|
self.bodyTextView.dataDetectorTypes
|
|
|
|
self.bodyTextView.dataDetectorTypes
|
|
|
@ -206,24 +210,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)hasBubbleBackground
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (self.cellType) {
|
|
|
|
|
|
|
|
case OWSMessageCellType_Unknown:
|
|
|
|
|
|
|
|
case OWSMessageCellType_TextMessage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_OversizeTextMessage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_GenericAttachment:
|
|
|
|
|
|
|
|
case OWSMessageCellType_DownloadingAttachment:
|
|
|
|
|
|
|
|
case OWSMessageCellType_ContactShare:
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
case OWSMessageCellType_StillImage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_AnimatedImage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_Audio:
|
|
|
|
|
|
|
|
case OWSMessageCellType_Video:
|
|
|
|
|
|
|
|
return self.hasBodyText;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)hasBodyTextContent
|
|
|
|
- (BOOL)hasBodyTextContent
|
|
|
|
{
|
|
|
|
{
|
|
|
|
switch (self.cellType) {
|
|
|
|
switch (self.cellType) {
|
|
|
@ -260,17 +246,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
[self.bubbleView addSubview:self.stackView];
|
|
|
|
[self.bubbleView addSubview:self.stackView];
|
|
|
|
[self.viewConstraints addObjectsFromArray:[self.stackView autoPinEdgesToSuperviewEdges]];
|
|
|
|
[self.viewConstraints addObjectsFromArray:[self.stackView autoPinEdgesToSuperviewEdges]];
|
|
|
|
|
|
|
|
NSMutableArray<UIView *> *textViews = [NSMutableArray new];
|
|
|
|
|
|
|
|
|
|
|
|
if ([self.viewItem.interaction isKindOfClass:[TSMessage class]] && self.hasBubbleBackground) {
|
|
|
|
if (self.shouldShowSenderName) {
|
|
|
|
TSMessage *message = (TSMessage *)self.viewItem.interaction;
|
|
|
|
[self configureSenderNameLabel];
|
|
|
|
self.bubbleView.bubbleColor = [self.bubbleFactory bubbleColorWithMessage:message];
|
|
|
|
[textViews addObject:self.senderNameLabel];
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Media-only messages should have no background color; they will fill the bubble's bounds
|
|
|
|
|
|
|
|
// and we don't want artifacts at the edges.
|
|
|
|
|
|
|
|
self.bubbleView.bubbleColor = nil;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (self.isQuotedReply) {
|
|
|
|
if (self.isQuotedReply) {
|
|
|
|
|
|
|
|
// Flush any pending "text" subviews.
|
|
|
|
|
|
|
|
[self insertAnyTextViewsIntoStackView:textViews];
|
|
|
|
|
|
|
|
[textViews removeAllObjects];
|
|
|
|
|
|
|
|
|
|
|
|
BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class];
|
|
|
|
BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class];
|
|
|
|
DisplayableText *_Nullable displayableQuotedText
|
|
|
|
DisplayableText *_Nullable displayableQuotedText
|
|
|
|
= (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil);
|
|
|
|
= (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil);
|
|
|
@ -328,10 +315,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BOOL shouldFooterOverlayMedia = NO;
|
|
|
|
if (bodyMediaView) {
|
|
|
|
if (bodyMediaView) {
|
|
|
|
OWSAssert(self.loadCellContentBlock);
|
|
|
|
OWSAssert(self.loadCellContentBlock);
|
|
|
|
OWSAssert(self.unloadCellContentBlock);
|
|
|
|
OWSAssert(self.unloadCellContentBlock);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shouldFooterOverlayMedia = self.canFooterOverlayMedia;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Flush any pending "text" subviews.
|
|
|
|
|
|
|
|
[self insertAnyTextViewsIntoStackView:textViews];
|
|
|
|
|
|
|
|
[textViews removeAllObjects];
|
|
|
|
|
|
|
|
|
|
|
|
bodyMediaView.clipsToBounds = YES;
|
|
|
|
bodyMediaView.clipsToBounds = YES;
|
|
|
|
|
|
|
|
|
|
|
|
self.bodyMediaView = bodyMediaView;
|
|
|
|
self.bodyMediaView = bodyMediaView;
|
|
|
@ -359,72 +353,124 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UIStackView *_Nullable textStackView = nil;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We render malformed messages as "empty text" messages,
|
|
|
|
// We render malformed messages as "empty text" messages,
|
|
|
|
// so create a text view if there is no body media view.
|
|
|
|
// so create a text view if there is no body media view.
|
|
|
|
if (self.hasBodyText || !bodyMediaView) {
|
|
|
|
if (self.hasBodyText || !bodyMediaView) {
|
|
|
|
OWSMessageTextView *_Nullable bodyTextView = nil;
|
|
|
|
[self configureBodyTextView];
|
|
|
|
bodyTextView = [self configureBodyTextView];
|
|
|
|
[textViews addObject:self.bodyTextView];
|
|
|
|
|
|
|
|
|
|
|
|
textStackView = [UIStackView new];
|
|
|
|
|
|
|
|
textStackView.axis = UILayoutConstraintAxisVertical;
|
|
|
|
|
|
|
|
textStackView.alignment = UIStackViewAlignmentFill;
|
|
|
|
|
|
|
|
// TODO: Review
|
|
|
|
|
|
|
|
textStackView.spacing = self.textViewVSpacing;
|
|
|
|
|
|
|
|
textStackView.layoutMarginsRelativeArrangement = YES;
|
|
|
|
|
|
|
|
textStackView.layoutMargins = UIEdgeInsetsMake(self.conversationStyle.textInsetTop,
|
|
|
|
|
|
|
|
self.conversationStyle.textInsetHorizontal,
|
|
|
|
|
|
|
|
self.conversationStyle.textInsetBottom,
|
|
|
|
|
|
|
|
self.conversationStyle.textInsetHorizontal);
|
|
|
|
|
|
|
|
[self.stackView addArrangedSubview:textStackView];
|
|
|
|
|
|
|
|
[textStackView addArrangedSubview:bodyTextView];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
[bodyTextView autoSetDimension:ALDimensionHeight toSize:bodyTextContentSize.height],
|
|
|
|
[self.bodyTextView autoSetDimension:ALDimensionHeight toSize:bodyTextContentSize.height],
|
|
|
|
]];
|
|
|
|
]];
|
|
|
|
|
|
|
|
|
|
|
|
UIView *_Nullable tapForMoreLabel = [self createTapForMoreLabelIfNecessary];
|
|
|
|
UIView *_Nullable tapForMoreLabel = [self createTapForMoreLabelIfNecessary];
|
|
|
|
if (tapForMoreLabel) {
|
|
|
|
if (tapForMoreLabel) {
|
|
|
|
[textStackView addArrangedSubview:tapForMoreLabel];
|
|
|
|
[textViews addObject:tapForMoreLabel];
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
[tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight],
|
|
|
|
[tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight],
|
|
|
|
]];
|
|
|
|
]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OWSMessageFooterView *footerView = self.footerView;
|
|
|
|
if (self.viewItem.shouldHideFooter) {
|
|
|
|
[footerView configureWithConversationViewItem:self.viewItem];
|
|
|
|
// Do nothing.
|
|
|
|
if (textStackView) {
|
|
|
|
} else if (shouldFooterOverlayMedia) {
|
|
|
|
// Display footer below text.
|
|
|
|
OWSAssert(bodyMediaView);
|
|
|
|
[textStackView addArrangedSubview:self.footerView];
|
|
|
|
[self.footerView configureWithConversationViewItem:self.viewItem hasShadows:YES];
|
|
|
|
[self.footerView setHasShadows:NO viewItem:self.viewItem];
|
|
|
|
[bodyMediaView addSubview:self.footerView];
|
|
|
|
} else if (bodyMediaView) {
|
|
|
|
|
|
|
|
// Display footer over media.
|
|
|
|
|
|
|
|
[bodyMediaView addSubview:footerView];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bodyMediaView.layoutMargins = UIEdgeInsetsZero;
|
|
|
|
bodyMediaView.layoutMargins = UIEdgeInsetsZero;
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
[footerView autoPinLeadingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal],
|
|
|
|
[self.footerView autoPinLeadingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal],
|
|
|
|
[footerView autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal],
|
|
|
|
[self.footerView autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal],
|
|
|
|
[footerView autoPinBottomToSuperviewMarginWithInset:self.conversationStyle.textInsetBottom],
|
|
|
|
[self.footerView autoPinBottomToSuperviewMarginWithInset:self.conversationStyle.textInsetBottom],
|
|
|
|
]];
|
|
|
|
]];
|
|
|
|
[self.footerView setHasShadows:YES viewItem:self.viewItem];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
OWSFail(@"%@ could not display footer.", self.logTag);
|
|
|
|
[self.footerView configureWithConversationViewItem:self.viewItem hasShadows:NO];
|
|
|
|
|
|
|
|
[textViews addObject:self.footerView];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (textStackView) {
|
|
|
|
[self insertAnyTextViewsIntoStackView:textViews];
|
|
|
|
CGSize bubbleSize = [self measureSize];
|
|
|
|
|
|
|
|
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired
|
|
|
|
CGSize bubbleSize = [self measureSize];
|
|
|
|
forConstraints:^{
|
|
|
|
[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
forConstraints:^{
|
|
|
|
[self autoSetDimension:ALDimensionWidth toSize:bubbleSize.width],
|
|
|
|
[self.viewConstraints addObjectsFromArray:@[
|
|
|
|
]];
|
|
|
|
[self autoSetDimension:ALDimensionWidth toSize:bubbleSize.width],
|
|
|
|
}];
|
|
|
|
]];
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self updateBubbleColorWithBodyMediaView:bodyMediaView];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateBubbleColorWithBodyMediaView:(nullable UIView *)bodyMediaView
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasOnlyBodyMediaView) {
|
|
|
|
|
|
|
|
TSMessage *message = (TSMessage *)self.viewItem.interaction;
|
|
|
|
|
|
|
|
self.bubbleView.bubbleColor = [self.bubbleFactory bubbleColorWithMessage:message];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Media-only messages should have no background color; they will fill the bubble's bounds
|
|
|
|
|
|
|
|
// and we don't want artifacts at the edges.
|
|
|
|
|
|
|
|
self.bubbleView.bubbleColor = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)canFooterOverlayMedia
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (self.cellType) {
|
|
|
|
|
|
|
|
case OWSMessageCellType_Unknown:
|
|
|
|
|
|
|
|
case OWSMessageCellType_TextMessage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_OversizeTextMessage:
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
case OWSMessageCellType_StillImage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_AnimatedImage:
|
|
|
|
|
|
|
|
case OWSMessageCellType_Video:
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
case OWSMessageCellType_Audio:
|
|
|
|
|
|
|
|
case OWSMessageCellType_GenericAttachment:
|
|
|
|
|
|
|
|
case OWSMessageCellType_DownloadingAttachment:
|
|
|
|
|
|
|
|
case OWSMessageCellType_ContactShare:
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)insertAnyTextViewsIntoStackView:(NSArray<UIView *> *)textViews
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (textViews.count < 1) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
UIStackView *textStackView = [[UIStackView alloc] initWithArrangedSubviews:textViews];
|
|
|
|
|
|
|
|
textStackView.axis = UILayoutConstraintAxisVertical;
|
|
|
|
|
|
|
|
textStackView.alignment = UIStackViewAlignmentFill;
|
|
|
|
|
|
|
|
// TODO: Review
|
|
|
|
|
|
|
|
textStackView.spacing = self.textViewVSpacing;
|
|
|
|
|
|
|
|
textStackView.layoutMarginsRelativeArrangement = YES;
|
|
|
|
|
|
|
|
textStackView.layoutMargins = UIEdgeInsetsMake(self.conversationStyle.textInsetTop,
|
|
|
|
|
|
|
|
self.conversationStyle.textInsetHorizontal,
|
|
|
|
|
|
|
|
self.conversationStyle.textInsetBottom,
|
|
|
|
|
|
|
|
self.conversationStyle.textInsetHorizontal);
|
|
|
|
|
|
|
|
[self.stackView addArrangedSubview:textStackView];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We now eagerly create our view hierarchy (to do this exactly once per cell usage)
|
|
|
|
// We now eagerly create our view hierarchy (to do this exactly once per cell usage)
|
|
|
|
// but lazy-load any expensive media (photo, gif, etc.) used in those views. Note that
|
|
|
|
// but lazy-load any expensive media (photo, gif, etc.) used in those views. Note that
|
|
|
|
// this lazy-load can fail, in which case we modify the view hierarchy to use an "error"
|
|
|
|
// this lazy-load can fail, in which case we modify the view hierarchy to use an "error"
|
|
|
@ -464,7 +510,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
- (CGFloat)textViewVSpacing
|
|
|
|
- (CGFloat)textViewVSpacing
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return 5.f;
|
|
|
|
return 2.f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Load / Unload
|
|
|
|
#pragma mark - Load / Unload
|
|
|
@ -485,7 +531,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Subviews
|
|
|
|
#pragma mark - Subviews
|
|
|
|
|
|
|
|
|
|
|
|
- (OWSMessageTextView *)configureBodyTextView
|
|
|
|
- (void)configureBodyTextView
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(self.hasBodyText);
|
|
|
|
OWSAssert(self.hasBodyText);
|
|
|
|
|
|
|
|
|
|
|
@ -501,7 +547,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
textColor:self.bodyTextColor
|
|
|
|
textColor:self.bodyTextColor
|
|
|
|
font:self.textMessageFont
|
|
|
|
font:self.textMessageFont
|
|
|
|
shouldIgnoreEvents:shouldIgnoreEvents];
|
|
|
|
shouldIgnoreEvents:shouldIgnoreEvents];
|
|
|
|
return self.bodyTextView;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (void)loadForTextDisplay:(OWSMessageTextView *)textView
|
|
|
|
+ (void)loadForTextDisplay:(OWSMessageTextView *)textView
|
|
|
@ -525,6 +570,22 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
textView.text = text;
|
|
|
|
textView.text = text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)shouldShowSenderName
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return self.viewItem.senderName.length > 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)configureSenderNameLabel
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(self.senderNameLabel);
|
|
|
|
|
|
|
|
OWSAssert(self.shouldShowSenderName);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.senderNameLabel.text = self.viewItem.senderName.uppercaseString;
|
|
|
|
|
|
|
|
self.senderNameLabel.textColor = self.bodyTextColor;
|
|
|
|
|
|
|
|
self.senderNameLabel.font = UIFont.ows_dynamicTypeCaption2Font;
|
|
|
|
|
|
|
|
self.senderNameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)hasTapForMore
|
|
|
|
- (BOOL)hasTapForMore
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (!self.hasBodyText) {
|
|
|
|
if (!self.hasBodyText) {
|
|
|
@ -854,9 +915,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
const int maxTextWidth = (int)floor(self.conversationStyle.maxMessageWidth - hMargins);
|
|
|
|
const int maxTextWidth = (int)floor(self.conversationStyle.maxMessageWidth - hMargins);
|
|
|
|
|
|
|
|
|
|
|
|
OWSMessageTextView *bodyTextView = [self configureBodyTextView];
|
|
|
|
[self configureBodyTextView];
|
|
|
|
const int kMaxIterations = 5;
|
|
|
|
const int kMaxIterations = 5;
|
|
|
|
CGSize result = [bodyTextView compactSizeThatFitsMaxWidth:maxTextWidth maxIterations:kMaxIterations];
|
|
|
|
CGSize result = [self.bodyTextView compactSizeThatFitsMaxWidth:maxTextWidth maxIterations:kMaxIterations];
|
|
|
|
|
|
|
|
|
|
|
|
if (includeMargins) {
|
|
|
|
if (includeMargins) {
|
|
|
|
result.width += hMargins;
|
|
|
|
result.width += hMargins;
|
|
|
@ -961,6 +1022,34 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
return CGSizeCeil(result);
|
|
|
|
return CGSizeCeil(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (CGSize)senderNameSizeWithBodyMediaSize:(CGSize)bodyMediaSize includeMargins:(BOOL)includeMargins
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(self.conversationStyle);
|
|
|
|
|
|
|
|
OWSAssert(self.conversationStyle.maxMessageWidth > 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!self.shouldShowSenderName) {
|
|
|
|
|
|
|
|
return CGSizeZero;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CGFloat hMargins = self.conversationStyle.textInsetHorizontal * 2;
|
|
|
|
|
|
|
|
const int maxTextWidth = (int)floor(self.conversationStyle.maxMessageWidth - hMargins);
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (CGSize)measureSize
|
|
|
|
- (CGSize)measureSize
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(self.conversationStyle);
|
|
|
|
OWSAssert(self.conversationStyle);
|
|
|
@ -970,13 +1059,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
CGSize cellSize = CGSizeZero;
|
|
|
|
CGSize cellSize = CGSizeZero;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Reflect "sender name" and "footer" layout.
|
|
|
|
|
|
|
|
// shouldFooterOverlayMedia = self.canFooterOverlayMedia;
|
|
|
|
|
|
|
|
|
|
|
|
CGSize quotedMessageSize = [self quotedMessageSize];
|
|
|
|
CGSize quotedMessageSize = [self quotedMessageSize];
|
|
|
|
cellSize.width = MAX(cellSize.width, quotedMessageSize.width);
|
|
|
|
cellSize.width = MAX(cellSize.width, quotedMessageSize.width);
|
|
|
|
cellSize.height += quotedMessageSize.height;
|
|
|
|
cellSize.height += quotedMessageSize.height;
|
|
|
|
|
|
|
|
|
|
|
|
CGSize mediaContentSize = [self bodyMediaSize];
|
|
|
|
CGSize bodyMediaSize = [self bodyMediaSize];
|
|
|
|
cellSize.width = MAX(cellSize.width, mediaContentSize.width);
|
|
|
|
cellSize.width = MAX(cellSize.width, bodyMediaSize.width);
|
|
|
|
cellSize.height += mediaContentSize.height;
|
|
|
|
cellSize.height += bodyMediaSize.height;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CGSize senderNameSize = [self senderNameSizeWithBodyMediaSize:bodyMediaSize includeMargins:YES];
|
|
|
|
|
|
|
|
cellSize.width = MAX(cellSize.width, senderNameSize.width);
|
|
|
|
|
|
|
|
cellSize.height += senderNameSize.height;
|
|
|
|
|
|
|
|
|
|
|
|
CGSize textContentSize = [self bodyTextSizeWithIncludeMargins:YES];
|
|
|
|
CGSize textContentSize = [self bodyTextSizeWithIncludeMargins:YES];
|
|
|
|
cellSize.width = MAX(cellSize.width, textContentSize.width);
|
|
|
|
cellSize.width = MAX(cellSize.width, textContentSize.width);
|
|
|
@ -993,10 +1089,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Update this to reflect generic attachment, downloading attachments and
|
|
|
|
// TODO: Update this to reflect generic attachment, downloading attachments and
|
|
|
|
// contact shares.
|
|
|
|
// contact shares.
|
|
|
|
if (self.hasFooter && self.hasBodyText) {
|
|
|
|
if (!self.viewItem.shouldHideFooter && !self.canFooterOverlayMedia) {
|
|
|
|
CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem];
|
|
|
|
CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem];
|
|
|
|
cellSize.width = MAX(cellSize.width, footerSize.width + self.conversationStyle.textInsetHorizontal * 2);
|
|
|
|
cellSize.width = MAX(cellSize.width, footerSize.width + self.conversationStyle.textInsetHorizontal * 2);
|
|
|
|
cellSize.height += self.textViewVSpacing + footerSize.height;
|
|
|
|
cellSize.height += self.textViewVSpacing + footerSize.height;
|
|
|
|
|
|
|
|
if (!self.hasBodyText) {
|
|
|
|
|
|
|
|
cellSize.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cellSize = CGSizeCeil(cellSize);
|
|
|
|
cellSize = CGSizeCeil(cellSize);
|
|
|
@ -1004,12 +1103,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
return cellSize;
|
|
|
|
return cellSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)hasFooter
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (UIFont *)tapForMoreFont
|
|
|
|
- (UIFont *)tapForMoreFont
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return UIFont.ows_dynamicTypeCaption1Font;
|
|
|
|
return UIFont.ows_dynamicTypeCaption1Font;
|
|
|
|