Tweak message cells.

This commit is contained in:
Matthew Chen 2018-06-22 13:48:23 -04:00
parent 825e3f4ac0
commit ac6f78a5fc
29 changed files with 595 additions and 370 deletions

View File

@ -224,6 +224,7 @@
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; };
34D920E220DD39EA00D51158 /* ConversationLayoutInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E120DD39E900D51158 /* ConversationLayoutInfo.swift */; };
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; };
34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */; };
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */; };
@ -881,6 +882,7 @@
34D8C0291ED3685800188D7C /* DebugUIContacts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIContacts.h; sourceTree = "<group>"; };
34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = "<group>"; };
34D913491F62D4A500722898 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = "<group>"; };
34D920E120DD39E900D51158 /* ConversationLayoutInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationLayoutInfo.swift; sourceTree = "<group>"; };
34D99C8A1F27B13B00D284D6 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSViewController.h; sourceTree = "<group>"; };
34D99C8B1F27B13B00D284D6 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSViewController.m; sourceTree = "<group>"; };
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = "<group>"; };
@ -1496,10 +1498,12 @@
34D1F0951F867BFC0066283D /* Cells */,
34D1F0B21F86D31D0066283D /* ConversationCollectionView.h */,
34D1F0B31F86D31D0066283D /* ConversationCollectionView.m */,
45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */,
34D1F0671F8678AA0066283D /* ConversationInputTextView.h */,
34D1F0681F8678AA0066283D /* ConversationInputTextView.m */,
34D1F0691F8678AA0066283D /* ConversationInputToolbar.h */,
34D1F06A1F8678AA0066283D /* ConversationInputToolbar.m */,
34D920E120DD39E900D51158 /* ConversationLayoutInfo.swift */,
343A65971FC4CFE7000477A1 /* ConversationScrollButton.h */,
343A65961FC4CFE6000477A1 /* ConversationScrollButton.m */,
34D1F06D1F8678AA0066283D /* ConversationViewController.h */,
@ -1508,7 +1512,6 @@
34D1F0701F8678AA0066283D /* ConversationViewItem.m */,
34D1F0711F8678AA0066283D /* ConversationViewLayout.h */,
34D1F0721F8678AA0066283D /* ConversationViewLayout.m */,
45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */,
);
path = ConversationView;
sourceTree = "<group>";
@ -3310,6 +3313,7 @@
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */,
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,
34D920E220DD39EA00D51158 /* ConversationLayoutInfo.swift in Sources */,
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */,
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,

View File

@ -4,6 +4,7 @@
NS_ASSUME_NONNULL_BEGIN
@class ConversationLayoutInfo;
@class ConversationViewCell;
@class ConversationViewItem;
@class OWSContactOffersInteraction;
@ -69,12 +70,11 @@ NS_ASSUME_NONNULL_BEGIN
// * Users enters another view (e.g. conversation settings view, call screen, etc.).
@property (nonatomic) BOOL isCellVisible;
// The width of the collection view.
@property (nonatomic) int contentWidth;
@property (nonatomic, nullable) ConversationLayoutInfo *layoutInfo;
- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction;
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth;
- (CGSize)cellSize;
@end

View File

@ -16,17 +16,18 @@ NS_ASSUME_NONNULL_BEGIN
self.viewItem = nil;
self.delegate = nil;
self.isCellVisible = NO;
self.contentWidth = 0;
self.layoutInfo = nil;
}
- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSFail(@"%@ This method should be overridden.", self.logTag);
OWS_ABSTRACT_METHOD();
}
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
- (CGSize)cellSize
{
OWSFail(@"%@ This method should be overridden.", self.logTag);
OWS_ABSTRACT_METHOD();
return CGSizeZero;
}

View File

@ -4,6 +4,7 @@
#import "OWSContactOffersCell.h"
#import "ConversationViewItem.h"
#import "Signal-Swift.h"
#import <SignalMessaging/OWSContactOffersInteraction.h>
#import <SignalMessaging/UIColor+OWS.h>
#import <SignalMessaging/UIFont+OWS.h>
@ -38,6 +39,11 @@ NS_ASSUME_NONNULL_BEGIN
{
OWSAssert(!self.titleLabel);
self.preservesSuperviewLayoutMargins = NO;
self.contentView.preservesSuperviewLayoutMargins = NO;
self.layoutMargins = UIEdgeInsetsZero;
self.contentView.layoutMargins = UIEdgeInsetsZero;
// [self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.titleLabel = [UILabel new];
@ -169,15 +175,17 @@ NS_ASSUME_NONNULL_BEGIN
layoutButton(self.blockButton, interaction.hasBlockOffer);
}
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
- (CGSize)cellSize
{
OWSAssert(self.layoutInfo);
OWSAssert(self.layoutInfo.viewWidth > 0);
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]);
OWSContactOffersInteraction *interaction = (OWSContactOffersInteraction *)self.viewItem.interaction;
// TODO: Should we use viewWidth?
CGSize result = CGSizeMake(viewWidth, 0);
CGSize result = CGSizeMake(self.layoutInfo.viewWidth, 0);
result.height += self.topVMargin;
result.height += self.bottomVMargin;

View File

@ -5,6 +5,7 @@
NS_ASSUME_NONNULL_BEGIN
@class ContactShareViewModel;
@class ConversationLayoutInfo;
@class ConversationViewItem;
@class OWSContact;
@class OWSQuotedReplyModel;
@ -53,11 +54,13 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
@end
#pragma mark -
@interface OWSMessageBubbleView : UIView
@property (nonatomic, nullable) ConversationViewItem *viewItem;
@property (nonatomic) int contentWidth;
@property (nonatomic) ConversationLayoutInfo *layoutInfo;
@property (nonatomic) NSCache *cellMediaCache;
@ -78,7 +81,7 @@ typedef NS_ENUM(NSUInteger, OWSMessageGestureLocation) {
- (void)loadContent;
- (void)unloadContent;
- (CGSize)sizeForContentWidth:(int)contentWidth;
- (CGSize)measureSize;
- (void)prepareForReuse;

View File

@ -237,14 +237,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)configureViews
{
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
OWSAssert(self.viewItem.interaction);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
OWSAssert(self.contentWidth > 0);
CGSize quotedMessageContentSize = [self quotedMessageSizeForContentWidth:self.contentWidth includeMargins:NO];
CGSize bodyMediaContentSize = [self bodyMediaSizeForContentWidth:self.contentWidth];
CGSize bodyTextContentSize = [self bodyTextSizeForContentWidth:self.contentWidth includeMargins:NO];
CGSize quotedMessageContentSize = [self quotedMessageSize];
CGSize bodyMediaContentSize = [self bodyMediaSize];
CGSize bodyTextContentSize = [self bodyTextSize:NO];
self.bubbleView.isOutgoing = self.isOutgoing;
self.bubbleView.hideTail = self.viewItem.shouldHideBubbleTail && !self.alwaysShowBubbleTail;
@ -855,18 +855,18 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Measurement
// Size of "message body" text, not quoted reply text.
- (CGSize)bodyTextSizeForContentWidth:(int)contentWidth includeMargins:(BOOL)includeMargins
- (CGSize)bodyTextSize:(BOOL)includeMargins
{
OWSAssert(self.layoutInfo);
OWSAssert(self.layoutInfo.maxMessageWidth > 0);
if (!self.hasBodyText) {
return CGSizeZero;
}
BOOL isRTL = self.isRTL;
CGFloat leftMargin = isRTL ? self.textTrailingMargin : self.textLeadingMargin;
CGFloat rightMargin = isRTL ? self.textLeadingMargin : self.textTrailingMargin;
CGFloat hMargins = self.textTrailingMargin + self.textLeadingMargin;
const int maxMessageWidth = [self maxMessageWidthForContentWidth:contentWidth];
const int maxTextWidth = (int)floor(maxMessageWidth - (leftMargin + rightMargin));
const int maxTextWidth = (int)floor(self.layoutInfo.maxMessageWidth - hMargins);
OWSMessageTextView *bodyTextView = [self configureBodyTextView];
CGSize textSize = CGSizeCeil([bodyTextView sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]);
@ -874,22 +874,27 @@ NS_ASSUME_NONNULL_BEGIN
CGSize result = textSize;
if (includeMargins) {
result.width += leftMargin + rightMargin;
result.width += hMargins;
result.height += self.textTopMargin + self.textBottomMargin;
}
return CGSizeCeil(result);
}
- (CGSize)bodyMediaSizeForContentWidth:(int)contentWidth
- (CGSize)bodyMediaSize
{
const int maxMessageWidth = [self maxMessageWidthForContentWidth:contentWidth];
OWSAssert(self.layoutInfo);
OWSAssert(self.layoutInfo.maxMessageWidth > 0);
CGFloat maxMessageWidth = self.layoutInfo.maxMessageWidth;
CGSize result = CGSizeZero;
switch (self.cellType) {
case OWSMessageCellType_Unknown:
case OWSMessageCellType_TextMessage:
case OWSMessageCellType_OversizeTextMessage: {
return CGSizeZero;
result = CGSizeZero;
break;
}
case OWSMessageCellType_StillImage:
case OWSMessageCellType_AnimatedImage:
@ -926,28 +931,32 @@ NS_ASSUME_NONNULL_BEGIN
}
return CGSizeRound(CGSizeMake(mediaWidth, mediaHeight));
break;
}
case OWSMessageCellType_Audio:
return CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight);
result = CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight);
break;
case OWSMessageCellType_GenericAttachment:
return CGSizeMake(maxMessageWidth, [OWSGenericAttachmentView bubbleHeight]);
result = CGSizeMake(maxMessageWidth, [OWSGenericAttachmentView bubbleHeight]);
break;
case OWSMessageCellType_DownloadingAttachment:
return CGSizeMake(200, 90);
result = CGSizeMake(200, 90);
break;
case OWSMessageCellType_ContactShare:
OWSAssert(self.viewItem.contactShare);
return CGSizeMake(
result = CGSizeMake(
maxMessageWidth, [OWSContactShareView bubbleHeightForContactShare:self.viewItem.contactShare]);
break;
}
return CGSizeCeil(result);
}
- (int)maxMessageWidthForContentWidth:(int)contentWidth
{
return (int)floor(contentWidth * 0.8f);
}
- (CGSize)quotedMessageSizeForContentWidth:(int)contentWidth includeMargins:(BOOL)includeMargins
- (CGSize)quotedMessageSize
{
OWSAssert(self.layoutInfo);
OWSAssert(self.layoutInfo.maxMessageWidth > 0);
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
@ -963,31 +972,28 @@ NS_ASSUME_NONNULL_BEGIN
[OWSQuotedMessageView quotedMessageViewForConversation:self.viewItem.quotedReply
displayableQuotedText:displayableQuotedText
isOutgoing:isOutgoing];
const int maxMessageWidth = [self maxMessageWidthForContentWidth:contentWidth];
CGSize result = [quotedMessageView sizeForMaxWidth:maxMessageWidth - kBubbleThornSideInset];
if (includeMargins) {
result.width += kBubbleThornSideInset;
}
return result;
CGSize result = [quotedMessageView sizeForMaxWidth:self.layoutInfo.maxMessageWidth];
return CGSizeCeil(result);
}
- (CGSize)sizeForContentWidth:(int)contentWidth
- (CGSize)measureSize
{
OWSAssert(self.layoutInfo);
OWSAssert(self.layoutInfo.viewWidth > 0);
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
CGSize cellSize = CGSizeZero;
CGSize quotedMessageSize = [self quotedMessageSizeForContentWidth:contentWidth includeMargins:YES];
CGSize quotedMessageSize = [self quotedMessageSize];
cellSize.width = MAX(cellSize.width, quotedMessageSize.width);
cellSize.height += quotedMessageSize.height;
CGSize mediaContentSize = [self bodyMediaSizeForContentWidth:contentWidth];
CGSize mediaContentSize = [self bodyMediaSize];
cellSize.width = MAX(cellSize.width, mediaContentSize.width);
cellSize.height += mediaContentSize.height;
CGSize textContentSize = [self bodyTextSizeForContentWidth:contentWidth includeMargins:YES];
CGSize textContentSize = [self bodyTextSize:YES];
cellSize.width = MAX(cellSize.width, textContentSize.width);
cellSize.height += textContentSize.height;

View File

@ -51,6 +51,9 @@ NS_ASSUME_NONNULL_BEGIN
// Ensure only called once.
OWSAssert(!self.messageBubbleView);
self.preservesSuperviewLayoutMargins = NO;
self.contentView.preservesSuperviewLayoutMargins = NO;
_viewConstraints = [NSMutableArray new];
self.layoutMargins = UIEdgeInsetsZero;
@ -97,6 +100,13 @@ NS_ASSUME_NONNULL_BEGIN
[self addGestureRecognizer:panGesture];
}
- (void)setLayoutInfo:(nullable ConversationLayoutInfo *)layoutInfo
{
[super setLayoutInfo:layoutInfo];
self.messageBubbleView.layoutInfo = layoutInfo;
}
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
@ -152,14 +162,13 @@ NS_ASSUME_NONNULL_BEGIN
- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
OWSAssert(self.viewItem.interaction);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
OWSAssert(self.contentWidth > 0);
OWSAssert(self.messageBubbleView);
self.messageBubbleView.viewItem = self.viewItem;
self.messageBubbleView.contentWidth = self.contentWidth;
self.messageBubbleView.cellMediaCache = self.delegate.cellMediaCache;
[self.messageBubbleView configureViews];
[self.messageBubbleView loadContent];
@ -209,7 +218,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)updateDateHeader
{
OWSAssert(self.contentWidth > 0);
OWSAssert(self.layoutInfo);
static NSDateFormatter *dateHeaderDateFormatter = nil;
static NSDateFormatter *dateHeaderTimeFormatter = nil;
@ -256,11 +265,9 @@ NS_ASSUME_NONNULL_BEGIN
self.dateHeaderLabel.hidden = NO;
[self.viewConstraints addObjectsFromArray:@[
// Date headers should be visually centered within the conversation view,
// so they need to extend outside the cell's boundaries.
[self.dateHeaderLabel autoSetDimension:ALDimensionWidth toSize:self.contentWidth],
(self.isIncoming ? [self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]
: [self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTrailing]),
// TODO: Are data headers symmetric or are they asymmetric? gutters are asymmetric?
[self.dateHeaderLabel autoPinLeadingToSuperviewMarginWithInset:self.layoutInfo.gutterLeading],
[self.dateHeaderLabel autoPinTrailingToSuperviewMarginWithInset:self.layoutInfo.gutterTrailing],
[self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTop],
[self.dateHeaderLabel autoSetDimension:ALDimensionHeight toSize:self.dateHeaderHeight],
]];
@ -362,7 +369,7 @@ NS_ASSUME_NONNULL_BEGIN
// we want to leave spaces for an expiration timer and
// include padding so that they still visually "cling" to the
// appropriate incoming/outgoing edge.
const CGFloat maxFooterLabelWidth = self.contentWidth - 100;
const CGFloat maxFooterLabelWidth = self.layoutInfo.maxFooterWidth;
if (hasExpirationTimer &&
attributedText) {
[self.viewConstraints addObjectsFromArray:@[
@ -411,16 +418,18 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Measurement
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
- (CGSize)cellSize
{
OWSAssert(self.layoutInfo);
OWSAssert(self.layoutInfo.viewWidth > 0);
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
OWSAssert(self.messageBubbleView);
self.messageBubbleView.viewItem = self.viewItem;
self.messageBubbleView.contentWidth = self.contentWidth;
self.messageBubbleView.cellMediaCache = self.delegate.cellMediaCache;
CGSize messageBubbleSize = [self.messageBubbleView sizeForContentWidth:contentWidth];
CGSize messageBubbleSize = [self.messageBubbleView measureSize];
CGSize cellSize = messageBubbleSize;

View File

@ -4,6 +4,7 @@
#import "OWSSystemMessageCell.h"
#import "ConversationViewItem.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
@ -22,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) UIImageView *imageView;
@property (nonatomic) UILabel *titleLabel;
@property (nonatomic) UIStackView *stackView;
@property (nonatomic) NSArray<NSLayoutConstraint *> *layoutConstraints;
@end
@ -43,19 +46,31 @@ NS_ASSUME_NONNULL_BEGIN
{
OWSAssert(!self.imageView);
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.preservesSuperviewLayoutMargins = NO;
self.contentView.preservesSuperviewLayoutMargins = NO;
self.layoutMargins = UIEdgeInsetsZero;
self.contentView.layoutMargins = UIEdgeInsetsZero;
self.backgroundColor = [UIColor whiteColor];
self.imageView = [UIImageView new];
[self.contentView addSubview:self.imageView];
[self.imageView autoSetDimension:ALDimensionWidth toSize:self.iconSize];
[self.imageView autoSetDimension:ALDimensionHeight toSize:self.iconSize];
[self.imageView setContentHuggingHigh];
self.titleLabel = [UILabel new];
self.titleLabel.textColor = [UIColor colorWithRGBHex:0x403e3b];
self.titleLabel.font = [self titleFont];
self.titleLabel.numberOfLines = 0;
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.contentView addSubview:self.titleLabel];
self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
self.imageView,
self.titleLabel,
]];
self.stackView.axis = UILayoutConstraintAxisHorizontal;
self.stackView.spacing = self.hSpacing;
self.stackView.alignment = UIStackViewAlignmentCenter;
[self.contentView addSubview:self.stackView];
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
@ -66,6 +81,12 @@ NS_ASSUME_NONNULL_BEGIN
[self addGestureRecognizer:longPress];
}
- (void)configureFonts
{
// Update cell to reflect changes in dynamic text.
self.titleLabel.font = UIFont.ows_dynamicTypeFootnoteFont;
}
+ (NSString *)cellReuseIdentifier
{
return NSStringFromClass([self class]);
@ -73,6 +94,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
TSInteraction *interaction = self.viewItem.interaction;
@ -81,9 +103,27 @@ NS_ASSUME_NONNULL_BEGIN
self.imageView.image = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.imageView.tintColor = [self iconColorForInteraction:interaction];
self.titleLabel.textColor = [self textColor];
[self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction];
[self applyTitleForInteraction:interaction label:self.titleLabel];
[self setNeedsLayout];
CGSize titleSize = [self titleSize];
[NSLayoutConstraint deactivateConstraints:self.layoutConstraints];
self.layoutConstraints = @[
[self.titleLabel autoSetDimension:ALDimensionWidth toSize:titleSize.width],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.topVMargin],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.bottomVMargin],
[self.stackView autoHCenterInSuperview],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeLeading
withInset:self.layoutInfo.fullWidthGutterLeading + self.hMargin
relation:NSLayoutRelationGreaterThanOrEqual],
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
withInset:self.layoutInfo.fullWidthGutterTrailing + self.hMargin
relation:NSLayoutRelationGreaterThanOrEqual],
];
[self.contentView logFrameLaterWithLabel:@"contentView"];
[self.stackView logFrameLaterWithLabel:@"stackView"];
[self.titleLabel logFrameLaterWithLabel:@"titleLabel"];
}
- (UIColor *)textColor
@ -162,19 +202,17 @@ NS_ASSUME_NONNULL_BEGIN
- (void)applyTitleForInteraction:(TSInteraction *)interaction
label:(UILabel *)label
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(interaction);
OWSAssert(label);
// Update cell to reflect changes in dynamic text.
self.titleLabel.font = [self titleFont];
[self configureFonts];
// TODO: Should we move the copy generation into this view?
if ([interaction isKindOfClass:[TSErrorMessage class]]) {
TSErrorMessage *errorMessage = (TSErrorMessage *)interaction;
label.text = [errorMessage previewTextWithTransaction:transaction];
label.text = [errorMessage previewText];
} else if ([interaction isKindOfClass:[TSInfoMessage class]]) {
TSInfoMessage *infoMessage = (TSInfoMessage *)interaction;
if ([infoMessage isKindOfClass:[OWSVerificationStateChangeMessage class]]) {
@ -199,22 +237,17 @@ NS_ASSUME_NONNULL_BEGIN
@"another device. Embeds {{user's name or phone number}}.")));
label.text = [NSString stringWithFormat:titleFormat, displayName];
} else {
label.text = [infoMessage previewTextWithTransaction:transaction];
label.text = [infoMessage previewText];
}
} else if ([interaction isKindOfClass:[TSCall class]]) {
TSCall *call = (TSCall *)interaction;
label.text = [call previewTextWithTransaction:transaction];
label.text = [call previewText];
} else {
OWSFail(@"Unknown interaction type: %@", [interaction class]);
label.text = nil;
}
}
- (UIFont *)titleFont
{
return UIFont.ows_dynamicTypeFootnoteFont;
}
- (CGFloat)hMargin
{
return 25.f;
@ -240,48 +273,30 @@ NS_ASSUME_NONNULL_BEGIN
return 20.f;
}
- (void)layoutSubviews
- (CGSize)titleSize
{
[super layoutSubviews];
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
CGFloat maxTitleWidth = (self.contentView.width - ([self hMargin] * 2.f + [self hSpacing] + [self iconSize]));
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)];
CGFloat contentWidth = ([self iconSize] + [self hSpacing] + titleSize.width);
CGFloat contentLeft = round((self.contentView.width - contentWidth) * 0.5f);
CGFloat imageLeft = ([self isRTL] ? round(contentLeft + contentWidth - [self iconSize]) : contentLeft);
CGFloat titleLeft = ([self isRTL] ? contentLeft : round(imageLeft + [self iconSize] + [self hSpacing]));
self.imageView.frame = CGRectMake(
imageLeft, round((self.contentView.height - [self iconSize]) * 0.5f), [self iconSize], [self iconSize]);
self.titleLabel.frame = CGRectMake(titleLeft,
round((self.contentView.height - titleSize.height) * 0.5f),
ceil(titleSize.width + 1.f),
ceil(titleSize.height + 1.f));
CGFloat maxTitleWidth
= (CGFloat)floor(self.layoutInfo.fullWidthContentWidth - (2 * self.hMargin + self.iconSize + self.hSpacing));
return [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)];
}
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
- (CGSize)cellSize
{
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
TSInteraction *interaction = self.viewItem.interaction;
CGSize result = CGSizeMake(contentWidth, 0);
result.height += self.topVMargin;
result.height += self.bottomVMargin;
CGSize result = CGSizeMake(self.layoutInfo.viewWidth, 0);
// FIXME pass in transaction from the uiDBConnection.
[[TSYapDatabaseObject dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[self applyTitleForInteraction:interaction label:self.titleLabel transaction:transaction];
}];
CGFloat maxTitleWidth = (contentWidth - ([self hMargin] * 2.f + [self hSpacing] + [self iconSize]));
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(maxTitleWidth, CGFLOAT_MAX)];
[self applyTitleForInteraction:interaction label:self.titleLabel];
CGSize titleSize = [self titleSize];
CGFloat contentHeight = ceil(MAX([self iconSize], titleSize.height));
result.height += contentHeight;
result.height = (contentHeight + self.topVMargin + self.bottomVMargin);
return result;
}

View File

@ -4,6 +4,7 @@
#import "OWSUnreadIndicatorCell.h"
#import "ConversationViewItem.h"
#import "Signal-Swift.h"
#import <SignalMessaging/TSUnreadIndicatorInteraction.h>
#import <SignalMessaging/UIColor+OWS.h>
#import <SignalMessaging/UIFont+OWS.h>
@ -15,12 +16,14 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) TSUnreadIndicatorInteraction *interaction;
@property (nonatomic) UIView *bannerView;
@property (nonatomic) UIView *bannerTopHighlightView;
@property (nonatomic) UIView *bannerBottomHighlightView1;
@property (nonatomic) UIView *bannerBottomHighlightView2;
// TODO:
//@property (nonatomic) UIView *bannerView;
//@property (nonatomic) UIView *bannerTopHighlightView;
//@property (nonatomic) UIView *bannerBottomHighlightView1;
//@property (nonatomic) UIView *bannerBottomHighlightView2;
@property (nonatomic) UILabel *titleLabel;
@property (nonatomic) UILabel *subtitleLabel;
//@property (nonatomic) UILabel *subtitleLabel;
@property (nonatomic) NSArray<NSLayoutConstraint *> *layoutConstraints;
@end
@ -40,41 +43,64 @@ NS_ASSUME_NONNULL_BEGIN
- (void)commontInit
{
OWSAssert(!self.bannerView);
OWSAssert(!self.titleLabel);
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.preservesSuperviewLayoutMargins = NO;
self.contentView.preservesSuperviewLayoutMargins = NO;
self.layoutMargins = UIEdgeInsetsZero;
self.contentView.layoutMargins = UIEdgeInsetsZero;
self.backgroundColor = [UIColor whiteColor];
// self.backgroundColor = [UIColor whiteColor];
self.bannerView = [UIView new];
self.bannerView.backgroundColor = [UIColor colorWithRGBHex:0xf6eee3];
[self.contentView addSubview:self.bannerView];
self.bannerTopHighlightView = [UIView new];
self.bannerTopHighlightView.backgroundColor = [UIColor colorWithRGBHex:0xf9f3eb];
[self.bannerView addSubview:self.bannerTopHighlightView];
self.bannerBottomHighlightView1 = [UIView new];
self.bannerBottomHighlightView1.backgroundColor = [UIColor colorWithRGBHex:0xd1c6b8];
[self.bannerView addSubview:self.bannerBottomHighlightView1];
self.bannerBottomHighlightView2 = [UIView new];
self.bannerBottomHighlightView2.backgroundColor = [UIColor colorWithRGBHex:0xdbcfc0];
[self.bannerView addSubview:self.bannerBottomHighlightView2];
// self.bannerView = [UIView new];
// self.bannerView.backgroundColor = [UIColor colorWithRGBHex:0xf6eee3];
// [self.contentView addSubview:self.bannerView];
//
// self.bannerTopHighlightView = [UIView new];
// self.bannerTopHighlightView.backgroundColor = [UIColor colorWithRGBHex:0xf9f3eb];
// [self.bannerView addSubview:self.bannerTopHighlightView];
//
// self.bannerBottomHighlightView1 = [UIView new];
// self.bannerBottomHighlightView1.backgroundColor = [UIColor colorWithRGBHex:0xd1c6b8];
// [self.bannerView addSubview:self.bannerBottomHighlightView1];
//
// self.bannerBottomHighlightView2 = [UIView new];
// self.bannerBottomHighlightView2.backgroundColor = [UIColor colorWithRGBHex:0xdbcfc0];
// [self.bannerView addSubview:self.bannerBottomHighlightView2];
self.titleLabel = [UILabel new];
self.titleLabel.textColor = [UIColor colorWithRGBHex:0x403e3b];
self.titleLabel.font = [self titleFont];
[self.bannerView addSubview:self.titleLabel];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.titleLabel];
self.subtitleLabel = [UILabel new];
self.subtitleLabel.textColor = [UIColor ows_infoMessageBorderColor];
self.subtitleLabel.font = [self subtitleFont];
// The subtitle may wrap to a second line.
self.subtitleLabel.numberOfLines = 0;
self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.subtitleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.subtitleLabel];
// self.subtitleLabel = [UILabel new];
// self.subtitleLabel.textColor = [UIColor ows_infoMessageBorderColor];
// self.subtitleLabel.font = [self subtitleFont];
// // The subtitle may wrap to a second line.
// self.subtitleLabel.numberOfLines = 0;
// self.subtitleLabel.lineBreakMode = NSLineBreakByWordWrapping;
// self.subtitleLabel.textAlignment = NSTextAlignmentCenter;
// [self.contentView addSubview:self.subtitleLabel];
[self configureFonts];
}
- (void)configureFonts
{
// Update cell to reflect changes in dynamic text.
self.titleLabel.font = UIFont.ows_dynamicTypeBodyFont;
// self.subtitleLabel.font = [self subtitleFont];
// - (UIFont *)titleFont
// {
// return UIFont.ows_dynamicTypeBodyFont;
// }
//
// - (UIFont *)subtitleFont
// {
// return UIFont.ows_dynamicTypeCaption1Font;
// }
}
+ (NSString *)cellReuseIdentifier
@ -84,31 +110,29 @@ NS_ASSUME_NONNULL_BEGIN
- (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]);
[self configureFonts];
TSUnreadIndicatorInteraction *interaction = (TSUnreadIndicatorInteraction *)self.viewItem.interaction;
self.titleLabel.text = [self titleForInteraction:interaction];
self.subtitleLabel.text = [self subtitleForInteraction:interaction];
// Update cell to reflect changes in dynamic text.
self.titleLabel.font = [self titleFont];
self.subtitleLabel.font = [self subtitleFont];
// self.subtitleLabel.text = [self subtitleForInteraction:interaction];
self.backgroundColor = [UIColor whiteColor];
[self setNeedsLayout];
}
[NSLayoutConstraint deactivateConstraints:self.layoutConstraints];
self.layoutConstraints = @[
// TODO: Constants.
[self.titleLabel autoVCenterInSuperview],
[self.titleLabel autoPinLeadingToSuperviewMarginWithInset:self.layoutInfo.fullWidthGutterLeading],
[self.titleLabel autoPinTrailingToSuperviewMarginWithInset:self.layoutInfo.fullWidthGutterTrailing],
];
- (UIFont *)titleFont
{
return UIFont.ows_dynamicTypeBodyFont;
}
- (UIFont *)subtitleFont
{
return UIFont.ows_dynamicTypeCaption1Font;
// TODO:
// [self setNeedsLayout];
}
- (NSString *)titleForInteraction:(TSUnreadIndicatorInteraction *)interaction
@ -117,126 +141,130 @@ NS_ASSUME_NONNULL_BEGIN
.uppercaseString;
}
- (NSString *)subtitleForInteraction:(TSUnreadIndicatorInteraction *)interaction
{
if (!interaction.hasMoreUnseenMessages) {
return nil;
}
NSString *subtitleFormat = (interaction.missingUnseenSafetyNumberChangeCount > 0
? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_FORMAT",
@"Messages that indicates that there are more unseen messages that be revealed by tapping the 'load "
@"earlier messages' button. Embeds {{the name of the 'load earlier messages' button}}")
: NSLocalizedString(
@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES_FORMAT",
@"Messages that indicates that there are more unseen messages including safety number changes that "
@"be revealed by tapping the 'load earlier messages' button. Embeds {{the name of the 'load earlier "
@"messages' button}}."));
NSString *loadMoreButtonName = NSLocalizedString(
@"load_earlier_messages", @"Label for button that loads more messages in conversation view.");
return [NSString stringWithFormat:subtitleFormat, loadMoreButtonName];
}
- (CGFloat)subtitleHMargin
{
return 20.f;
}
- (CGFloat)subtitleVSpacing
{
return 3.f;
}
- (CGFloat)titleInnerHMargin
{
return 10.f;
}
- (CGFloat)titleVMargin
{
return 5.5f;
}
- (CGFloat)topVMargin
{
return 5.f;
}
- (CGFloat)bottomVMargin
{
return 5.f;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self.titleLabel sizeToFit];
// It's a bit of a hack, but we use a view that extends _outside_ the cell's bounds
// to draw its background, since we want the background to extend to the edges of the
// collection view.
//
// This layout logic assumes that the cell insets are symmetrical and can be deduced
// from the cell frame.
CGRect bannerViewFrame = CGRectMake(-self.left,
round(self.topVMargin),
round(self.width + self.left * 2.f),
round(self.titleLabel.height + self.titleVMargin * 2.f));
self.bannerView.frame = [self convertRect:bannerViewFrame toView:self.contentView];
// The highlights should be 1px (not 1pt), so adapt their thickness to
// the device resolution.
CGFloat kHighlightThickness = 1.f / [UIScreen mainScreen].scale;
self.bannerTopHighlightView.frame = CGRectMake(0, 0, self.bannerView.width, kHighlightThickness);
self.bannerBottomHighlightView1.frame
= CGRectMake(0, self.bannerView.height - kHighlightThickness * 2.f, self.bannerView.width, kHighlightThickness);
self.bannerBottomHighlightView2.frame
= CGRectMake(0, self.bannerView.height - kHighlightThickness * 1.f, self.bannerView.width, kHighlightThickness);
[self.titleLabel centerOnSuperview];
if (self.subtitleLabel.text.length > 0) {
CGSize subtitleSize = [self.subtitleLabel
sizeThatFits:CGSizeMake(self.contentView.width - [self subtitleHMargin] * 2.f, CGFLOAT_MAX)];
self.subtitleLabel.frame = CGRectMake(round((self.contentView.width - subtitleSize.width) * 0.5f),
round(self.bannerView.bottom + self.subtitleVSpacing),
ceil(subtitleSize.width),
ceil(subtitleSize.height));
}
}
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
//- (NSString *)subtitleForInteraction:(TSUnreadIndicatorInteraction *)interaction
//{
// if (!interaction.hasMoreUnseenMessages) {
// return nil;
// }
// NSString *subtitleFormat = (interaction.missingUnseenSafetyNumberChangeCount > 0
// ? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_FORMAT",
// @"Messages that indicates that there are more unseen messages that be revealed by tapping the 'load
// "
// @"earlier messages' button. Embeds {{the name of the 'load earlier messages' button}}")
// : NSLocalizedString(
// @"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES_FORMAT",
// @"Messages that indicates that there are more unseen messages including safety number changes that "
// @"be revealed by tapping the 'load earlier messages' button. Embeds {{the name of the 'load earlier
// "
// @"messages' button}}."));
// NSString *loadMoreButtonName = NSLocalizedString(
// @"load_earlier_messages", @"Label for button that loads more messages in conversation view.");
// return [NSString stringWithFormat:subtitleFormat, loadMoreButtonName];
//}
//- (CGFloat)subtitleHMargin
//{
// return 20.f;
//}
//
//- (CGFloat)subtitleVSpacing
//{
// return 3.f;
//}
//
//- (CGFloat)titleInnerHMargin
//{
// return 10.f;
//}
//
//- (CGFloat)titleVMargin
//{
// return 5.5f;
//}
//
//- (CGFloat)topVMargin
//{
// return 5.f;
//}
//
//- (CGFloat)bottomVMargin
//{
// return 5.f;
//}
//- (void)layoutSubviews
//{
// [super layoutSubviews];
//
// [self.titleLabel sizeToFit];
//
// // It's a bit of a hack, but we use a view that extends _outside_ the cell's bounds
// // to draw its background, since we want the background to extend to the edges of the
// // collection view.
// //
// // This layout logic assumes that the cell insets are symmetrical and can be deduced
// // from the cell frame.
// CGRect bannerViewFrame = CGRectMake(-self.left,
// round(self.topVMargin),
// round(self.width + self.left * 2.f),
// round(self.titleLabel.height + self.titleVMargin * 2.f));
// self.bannerView.frame = [self convertRect:bannerViewFrame toView:self.contentView];
//
// // The highlights should be 1px (not 1pt), so adapt their thickness to
// // the device resolution.
// CGFloat kHighlightThickness = 1.f / [UIScreen mainScreen].scale;
// self.bannerTopHighlightView.frame = CGRectMake(0, 0, self.bannerView.width, kHighlightThickness);
// self.bannerBottomHighlightView1.frame
// = CGRectMake(0, self.bannerView.height - kHighlightThickness * 2.f, self.bannerView.width,
// kHighlightThickness);
// self.bannerBottomHighlightView2.frame
// = CGRectMake(0, self.bannerView.height - kHighlightThickness * 1.f, self.bannerView.width,
// kHighlightThickness);
//
// [self.titleLabel centerOnSuperview];
//
// if (self.subtitleLabel.text.length > 0) {
// CGSize subtitleSize = [self.subtitleLabel
// sizeThatFits:CGSizeMake(self.contentView.width - [self subtitleHMargin] * 2.f, CGFLOAT_MAX)];
// self.subtitleLabel.frame = CGRectMake(round((self.contentView.width - subtitleSize.width) * 0.5f),
// round(self.bannerView.bottom + self.subtitleVSpacing),
// ceil(subtitleSize.width),
// ceil(subtitleSize.height));
// }
//}
- (CGSize)cellSize
{
OWSAssert(self.layoutInfo);
OWSAssert(self.viewItem);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]);
TSUnreadIndicatorInteraction *interaction = (TSUnreadIndicatorInteraction *)self.viewItem.interaction;
[self configureFonts];
// Update cell to reflect changes in dynamic text.
self.titleLabel.font = [self titleFont];
self.subtitleLabel.font = [self subtitleFont];
// TSUnreadIndicatorInteraction *interaction = (TSUnreadIndicatorInteraction *)self.viewItem.interaction;
// TODO: Should we use viewWidth?
CGSize result = CGSizeMake(viewWidth, 0);
result.height += self.titleVMargin * 2.f;
result.height += self.topVMargin;
result.height += self.bottomVMargin;
// TODO:
CGSize result = CGSizeMake(self.layoutInfo.fullWidthContentWidth, self.titleLabel.font.lineHeight + 24.f * 2);
// result.height += self.titleVMargin * 2.f;
// result.height += self.topVMargin;
// result.height += self.bottomVMargin;
//
// NSString *title = [self titleForInteraction:interaction];
// NSString *subtitle = [self subtitleForInteraction:interaction];
//
// self.titleLabel.text = title;
// result.height += ceil([self.titleLabel sizeThatFits:CGSizeZero].height);
//
// if (subtitle.length > 0) {
// result.height += self.subtitleVSpacing;
//
// self.subtitleLabel.text = subtitle;
// result.height += ceil(
// [self.subtitleLabel sizeThatFits:CGSizeMake(self.layoutInfo.fullWidthContentWidth -
// self.subtitleHMargin * 2.f, CGFLOAT_MAX)].height);
// }
NSString *title = [self titleForInteraction:interaction];
NSString *subtitle = [self subtitleForInteraction:interaction];
self.titleLabel.text = title;
result.height += ceil([self.titleLabel sizeThatFits:CGSizeZero].height);
if (subtitle.length > 0) {
result.height += self.subtitleVSpacing;
self.subtitleLabel.text = subtitle;
result.height += ceil(
[self.subtitleLabel sizeThatFits:CGSizeMake(viewWidth - self.subtitleHMargin * 2.f, CGFLOAT_MAX)].height);
}
return result;
return CGSizeCeil(result);
}
- (void)prepareForReuse

View File

@ -0,0 +1,73 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public class ConversationLayoutInfo: NSObject {
private let thread: TSThread
private let isRTL: Bool
// The width of the collection view.
@objc public var viewWidth: CGFloat = 0 {
didSet {
SwiftAssertIsOnMainThread(#function)
updateProperties()
}
}
@objc public let contentMarginTop: CGFloat = 0
@objc public let contentMarginBottom: CGFloat = 0
@objc public var gutterLeading: CGFloat = 0
@objc public var gutterTrailing: CGFloat = 0
// These are the gutters used by "full width" views
// like "date headers" and "unread indicator".
@objc public var fullWidthGutterLeading: CGFloat = 0
@objc public var fullWidthGutterTrailing: CGFloat = 0
// viewWidth - (gutterLeading + gutterTrailing)
@objc public var contentWidth: CGFloat = 0
// viewWidth - (gutterfullWidthGutterLeadingLeading + fullWidthGutterTrailing)
// TODO: Is this necessary?
@objc public var fullWidthContentWidth: CGFloat = 0
@objc public var maxMessageWidth: CGFloat = 0
@objc public var maxFooterWidth: CGFloat = 0
@objc
public required init(thread: TSThread) {
self.thread = thread
self.isRTL = CurrentAppContext().isRTL
super.init()
updateProperties()
}
private func updateProperties() {
if thread.isGroupThread() {
gutterLeading = 16
gutterTrailing = 20
} else {
gutterLeading = 40
gutterTrailing = 20
}
// TODO: Should these be symmetric?
fullWidthGutterLeading = gutterLeading
fullWidthGutterTrailing = gutterTrailing
contentWidth = viewWidth - (gutterLeading + gutterTrailing)
fullWidthContentWidth = viewWidth - (fullWidthGutterLeading + fullWidthGutterTrailing)
maxMessageWidth = floor(contentWidth * 0.8)
maxFooterWidth = floor(contentWidth - 100)
}
}

View File

@ -174,6 +174,7 @@ typedef enum : NSUInteger {
@property (nonatomic, readonly) ConversationInputToolbar *inputToolbar;
@property (nonatomic, readonly) ConversationCollectionView *collectionView;
@property (nonatomic, readonly) ConversationViewLayout *layout;
@property (nonatomic, readonly) ConversationLayoutInfo *layoutInfo;
@property (nonatomic) NSArray<ConversationViewItem *> *viewItems;
@property (nonatomic) NSMutableDictionary<NSString *, ConversationViewItem *> *viewItemCache;
@ -217,7 +218,6 @@ typedef enum : NSUInteger {
@property (nonatomic) UILabel *loadMoreHeader;
@property (nonatomic) uint64_t lastVisibleTimestamp;
@property (nonatomic, readonly) BOOL isGroupConversation;
@property (nonatomic) BOOL isUserScrolling;
@property (nonatomic) NSLayoutConstraint *scrollDownButtonButtomConstraint;
@ -356,6 +356,13 @@ typedef enum : NSUInteger {
object:nil];
}
- (BOOL)isGroupConversation
{
OWSAssert(self.thread);
return self.thread.isGroupThread;
}
- (void)signalAccountsDidChange:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
@ -435,12 +442,12 @@ typedef enum : NSUInteger {
OWSAssert(thread);
_thread = thread;
_isGroupConversation = [self.thread isKindOfClass:[TSGroupThread class]];
self.actionOnOpen = action;
self.focusMessageIdOnOpen = focusMessageId;
_cellMediaCache = [NSCache new];
// Cache the cell media for ~24 cells.
self.cellMediaCache.countLimit = 24;
_layoutInfo = [[ConversationLayoutInfo alloc] initWithThread:thread];
// We need to update the "unread indicator" _before_ we determine the initial range
// size, since it depends on where the unread indicator is placed.
@ -526,7 +533,11 @@ typedef enum : NSUInteger {
- (void)createContents
{
_layout = [ConversationViewLayout new];
OWSAssert(self.layoutInfo);
_layout = [[ConversationViewLayout alloc] initWithLayoutInfo:self.layoutInfo];
self.layoutInfo.viewWidth = self.view.width;
self.layout.delegate = self;
// We use the root view bounds as the initial frame for the collection
// view so that its contents can be laid out immediately.
@ -982,15 +993,16 @@ typedef enum : NSUInteger {
@"numbers of multiple users.")
: NSLocalizedString(@"VERIFY_PRIVACY",
@"Label for button or row which allows users to verify the safety "
@"number of another user."))style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
@"number of another user."))
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[weakSelf showNoLongerVerifiedUI];
}];
[actionSheetController addAction:verifyAction];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.dismissButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[weakSelf resetVerificationStateToDefault];
}];
[actionSheetController addAction:dismissAction];
@ -1656,7 +1668,7 @@ typedef enum : NSUInteger {
- (void)updateDisappearingMessagesConfiguration
{
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
self.disappearingMessagesConfiguration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:self.thread.uniqueId transaction:transaction];
}];
@ -1747,7 +1759,7 @@ typedef enum : NSUInteger {
UIAlertAction *deleteMessageAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[message remove];
}];
[actionSheetController addAction:deleteMessageAction];
@ -1755,17 +1767,17 @@ typedef enum : NSUInteger {
UIAlertAction *retryAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"MESSAGES_VIEW_FAILED_DOWNLOAD_RETRY_ACTION", @"Action sheet button text")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
OWSAttachmentsProcessor *processor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer
networkManager:self.networkManager];
[processor fetchAttachmentsForMessage:message
primaryStorage:self.primaryStorage
success:^(TSAttachmentStream *_Nonnull attachmentStream) {
success:^(TSAttachmentStream *attachmentStream) {
DDLogInfo(
@"%@ Successfully redownloaded attachment in thread: %@", self.logTag, message.thread);
}
failure:^(NSError *_Nonnull error) {
failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to redownload message with error: %@", self.logTag, error);
}];
}];
@ -1787,7 +1799,7 @@ typedef enum : NSUInteger {
UIAlertAction *deleteMessageAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_DELETE_TITLE", @"")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[message remove];
}];
[actionSheetController addAction:deleteMessageAction];
@ -1795,12 +1807,12 @@ typedef enum : NSUInteger {
UIAlertAction *resendMessageAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"SEND_AGAIN_BUTTON", @"")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[self.messageSender enqueueMessage:message
success:^{
DDLogInfo(@"%@ Successfully resent failed message.", self.logTag);
}
failure:^(NSError *_Nonnull error) {
failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to send message with error: %@", self.logTag, error);
}];
}];
@ -1922,7 +1934,7 @@ typedef enum : NSUInteger {
UIAlertAction *resetSessionAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"FINGERPRINT_SHRED_KEYMATERIAL_BUTTON", @"")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
if (![self.thread isKindOfClass:[TSContactThread class]]) {
// Corrupt Message errors only appear in contact threads.
DDLogError(@"%@ Unexpected request to reset session in group thread. Refusing", self.logTag);
@ -1955,7 +1967,7 @@ typedef enum : NSUInteger {
UIAlertAction *showSafteyNumberAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"SHOW_SAFETY_NUMBER_ACTION", @"Action sheet item")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
DDLogInfo(@"%@ Remote Key Changed actions: Show fingerprint display", self.logTag);
[self showFingerprintWithRecipientId:errorMessage.theirSignalId];
}];
@ -1964,7 +1976,7 @@ typedef enum : NSUInteger {
UIAlertAction *acceptSafetyNumberAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"ACCEPT_NEW_IDENTITY_ACTION", @"Action sheet item")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
DDLogInfo(@"%@ Remote Key Changed actions: Accepted new identity key", self.logTag);
// DEPRECATED: we're no longer creating these incoming SN error's per message,
@ -2000,7 +2012,7 @@ typedef enum : NSUInteger {
__weak ConversationViewController *weakSelf = self;
UIAlertAction *callAction = [UIAlertAction actionWithTitle:[CallStrings callBackAlertCallButton]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[weakSelf startAudioCall];
}];
[alertController addAction:callAction];
@ -2044,7 +2056,7 @@ typedef enum : NSUInteger {
actionWithTitle:NSLocalizedString(
@"BLOCK_OFFER_ACTIONSHEET_BLOCK_ACTION", @"Action sheet that will block an unknown user.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
DDLogInfo(@"%@ Blocking an unknown user.", self.logTag);
[self.blockingManager addBlockedPhoneNumber:interaction.recipientId];
// Delete the offers.
@ -2283,20 +2295,20 @@ typedef enum : NSUInteger {
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointer:attachmentPointer
networkManager:self.networkManager];
[self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[processor fetchAttachmentsForMessage:nil
transaction:transaction
success:^(TSAttachmentStream *_Nonnull attachmentStream) {
success:^(TSAttachmentStream *attachmentStream) {
[self.editingDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull postSuccessTransaction) {
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *postSuccessTransaction) {
[message setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[message saveWithTransaction:postSuccessTransaction];
}];
}
failure:^(NSError *_Nonnull error) {
failure:^(NSError *error) {
DDLogWarn(@"%@ Failed to redownload thumbnail with error: %@", self.logTag, error);
[self.editingDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull postSuccessTransaction) {
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *postSuccessTransaction) {
[message touchWithTransaction:transaction];
}];
}];
@ -2443,6 +2455,7 @@ typedef enum : NSUInteger {
MessageDetailViewController *view =
[[MessageDetailViewController alloc] initWithViewItem:conversationItem
message:message
thread:self.thread
mode:MessageMetadataViewModeFocusOnMetadata];
[self.navigationController pushViewController:view animated:YES];
}
@ -2452,7 +2465,7 @@ typedef enum : NSUInteger {
DDLogDebug(@"%@ user did tap reply", self.logTag);
__block OWSQuotedReplyModel *quotedReply;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
quotedReply = [OWSQuotedReplyModel quotedReplyForConversationViewItem:conversationItem transaction:transaction];
}];
@ -3739,7 +3752,7 @@ typedef enum : NSUInteger {
UIAlertAction *takeMediaAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"MEDIA_FROM_CAMERA_BUTTON", @"media picker option to take photo or video")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[self takePictureOrVideo];
}];
UIImage *takeMediaImage = [UIImage imageNamed:@"actionsheet_camera_black"];
@ -3750,7 +3763,7 @@ typedef enum : NSUInteger {
UIAlertAction *chooseMediaAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"MEDIA_FROM_LIBRARY_BUTTON", @"media picker option to choose from library")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[self chooseFromLibraryAsMedia];
}];
UIImage *chooseMediaImage = [UIImage imageNamed:@"actionsheet_camera_roll_black"];
@ -3761,7 +3774,7 @@ typedef enum : NSUInteger {
UIAlertAction *gifAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"SELECT_GIF_BUTTON", @"Label for 'select GIF to attach' action sheet button")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[self showGifPicker];
}];
UIImage *gifImage = [UIImage imageNamed:@"actionsheet_gif_black"];
@ -3773,7 +3786,7 @@ typedef enum : NSUInteger {
[UIAlertAction actionWithTitle:NSLocalizedString(@"MEDIA_FROM_DOCUMENT_PICKER_BUTTON",
@"action sheet button title when choosing attachment type")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[self showAttachmentDocumentPickerMenu];
}];
UIImage *chooseDocumentImage = [UIImage imageNamed:@"actionsheet_document_black"];
@ -3786,7 +3799,7 @@ typedef enum : NSUInteger {
[UIAlertAction actionWithTitle:NSLocalizedString(@"ATTACHMENT_MENU_CONTACT_BUTTON",
@"attachment menu option to send contact")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
handler:^(UIAlertAction *action) {
[self chooseContactForSending];
}];
UIImage *chooseContactImage = [UIImage imageNamed:@"actionsheet_contact"];
@ -3948,7 +3961,7 @@ typedef enum : NSUInteger {
successCompletion();
}
}
failure:^(NSError *_Nonnull error) {
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send group avatar update with error: %@", self.logTag, error);
}];
} else {
@ -3959,7 +3972,7 @@ typedef enum : NSUInteger {
successCompletion();
}
}
failure:^(NSError *_Nonnull error) {
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send group update with error: %@", self.logTag, error);
}];
}
@ -4754,6 +4767,7 @@ typedef enum : NSUInteger {
OWSAssertIsOnMainThread();
[self updateLastVisibleTimestamp];
self.layoutInfo.viewWidth = self.collectionView.width;
}
#pragma mark - View Items
@ -4799,7 +4813,8 @@ typedef enum : NSUInteger {
} else {
viewItem = [[ConversationViewItem alloc] initWithInteraction:interaction
isGroupThread:isGroupThread
transaction:transaction];
transaction:transaction
layoutInfo:self.layoutInfo];
}
viewItem.row = (NSInteger)row;
[viewItems addObject:viewItem];
@ -4966,9 +4981,9 @@ typedef enum : NSUInteger {
OWSMessageCell *messageCell = (OWSMessageCell *)cell;
messageCell.messageBubbleView.delegate = self;
}
cell.contentWidth = self.layout.contentWidth;
cell.layoutInfo = self.layoutInfo;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[cell loadForDisplayWithTransaction:transaction];
}];

View File

@ -66,10 +66,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
// previous update.
@property (nonatomic) NSInteger previousRow;
@property (nonatomic, readonly) ConversationLayoutInfo *layoutInfo;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithInteraction:(TSInteraction *)interaction
isGroupThread:(BOOL)isGroupThread
transaction:(YapDatabaseReadTransaction *)transaction;
transaction:(YapDatabaseReadTransaction *)transaction
layoutInfo:(ConversationLayoutInfo *)layoutInfo;
- (ConversationViewCell *)dequeueCellForCollectionView:(UICollectionView *)collectionView
indexPath:(NSIndexPath *)indexPath;

View File

@ -77,7 +77,12 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (instancetype)initWithInteraction:(TSInteraction *)interaction
isGroupThread:(BOOL)isGroupThread
transaction:(YapDatabaseReadTransaction *)transaction
layoutInfo:(ConversationLayoutInfo *)layoutInfo
{
OWSAssert(interaction);
OWSAssert(transaction);
OWSAssert(layoutInfo);
self = [super init];
if (!self) {
@ -86,6 +91,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
_interaction = interaction;
_isGroupThread = isGroupThread;
_layoutInfo = layoutInfo;
self.row = NSNotFound;
self.previousRow = NSNotFound;
@ -172,14 +178,16 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
self.cachedCellSize = nil;
}
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth
- (CGSize)cellSize
{
OWSAssertIsOnMainThread();
OWSAssert(self.layoutInfo);
if (!self.cachedCellSize) {
ConversationViewCell *_Nullable measurementCell = [self measurementCell];
measurementCell.viewItem = self;
CGSize cellSize = [measurementCell cellSizeForViewWidth:viewWidth contentWidth:contentWidth];
measurementCell.layoutInfo = self.layoutInfo;
CGSize cellSize = [measurementCell cellSize];
self.cachedCellSize = [NSValue valueWithCGSize:cellSize];
[measurementCell prepareForReuse];
}
@ -252,6 +260,14 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
return measurementCell;
}
- (CGFloat)vSpacingWithLastLayoutItem:(id<ConversationViewLayoutItem>)lastLayoutItem
{
OWSAssert(lastLayoutItem);
// TODO:
return 0.f;
}
- (ConversationViewCell *)dequeueCellForCollectionView:(UICollectionView *)collectionView
indexPath:(NSIndexPath *)indexPath
{

View File

@ -4,6 +4,7 @@
NS_ASSUME_NONNULL_BEGIN
// TODO: Remove this enum.
typedef NS_ENUM(NSInteger, ConversationViewLayoutAlignment) {
// We use incoming/outgoing, not left/right to support RTL.
ConversationViewLayoutAlignment_Incoming,
@ -12,12 +13,16 @@ typedef NS_ENUM(NSInteger, ConversationViewLayoutAlignment) {
ConversationViewLayoutAlignment_Center,
};
@class ConversationLayoutInfo;
@protocol ConversationViewLayoutItem <NSObject>
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth;
- (CGSize)cellSize;
- (ConversationViewLayoutAlignment)layoutAlignment;
- (CGFloat)vSpacingWithLastLayoutItem:(id<ConversationViewLayoutItem>)lastLayoutItem;
@end
#pragma mark -
@ -39,7 +44,11 @@ typedef NS_ENUM(NSInteger, ConversationViewLayoutAlignment) {
@property (nonatomic, weak) id<ConversationViewLayoutDelegate> delegate;
@property (nonatomic, readonly) BOOL hasLayout;
@property (nonatomic, readonly) BOOL hasEverHadLayout;
@property (nonatomic, readonly) int contentWidth;
@property (nonatomic, readonly) ConversationLayoutInfo *layoutInfo;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithLayoutInfo:(ConversationLayoutInfo *)layoutInfo;
@end

View File

@ -3,6 +3,7 @@
//
#import "ConversationViewLayout.h"
#import "Signal-Swift.h"
#import "UIView+OWS.h"
NS_ASSUME_NONNULL_BEGIN
@ -21,18 +22,17 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) BOOL hasLayout;
@property (nonatomic) BOOL hasEverHadLayout;
@property (nonatomic) int contentWidth;
@end
#pragma mark -
@implementation ConversationViewLayout
- (instancetype)init
- (instancetype)initWithLayoutInfo:(ConversationLayoutInfo *)layoutInfo
{
if (self = [super init]) {
_itemAttributesMap = [NSMutableDictionary new];
_layoutInfo = layoutInfo;
}
return self;
@ -94,48 +94,29 @@ NS_ASSUME_NONNULL_BEGIN
// TODO: Remove this log statement after we've reduced the invalidation churn.
DDLogVerbose(@"%@ prepareLayout", self.logTag);
const int vInset = 8;
const int hInset = 10;
const int vSpacing = 5;
const int viewWidth = (int)floor(self.collectionView.bounds.size.width);
const int contentWidth = (int)floor(viewWidth - 2 * hInset);
self.contentWidth = contentWidth;
const CGFloat viewWidth = self.layoutInfo.viewWidth;
NSArray<id<ConversationViewLayoutItem>> *layoutItems = self.delegate.layoutItems;
CGFloat y = vInset + self.delegate.layoutHeaderHeight;
CGFloat y = self.layoutInfo.contentMarginTop + self.delegate.layoutHeaderHeight;
CGFloat contentBottom = y;
BOOL isRTL = self.collectionView.isRTL;
NSInteger row = 0;
id<ConversationViewLayoutItem> _Nullable lastLayoutItem = nil;
for (id<ConversationViewLayoutItem> layoutItem in layoutItems) {
CGSize layoutSize = [layoutItem cellSizeForViewWidth:viewWidth contentWidth:contentWidth];
layoutSize.width = MIN(viewWidth, floor(layoutSize.width));
layoutSize.height = floor(layoutSize.height);
CGRect itemFrame;
switch (layoutItem.layoutAlignment) {
case ConversationViewLayoutAlignment_Incoming:
case ConversationViewLayoutAlignment_Outgoing: {
BOOL isIncoming = layoutItem.layoutAlignment == ConversationViewLayoutAlignment_Incoming;
BOOL isLeft = isIncoming ^ isRTL;
if (isLeft) {
itemFrame = CGRectMake(hInset, y, layoutSize.width, layoutSize.height);
} else {
itemFrame
= CGRectMake(viewWidth - (hInset + layoutSize.width), y, layoutSize.width, layoutSize.height);
}
break;
}
case ConversationViewLayoutAlignment_FullWidth:
itemFrame = CGRectMake(0, y, viewWidth, layoutSize.height);
break;
case ConversationViewLayoutAlignment_Center:
itemFrame = CGRectMake(
hInset + round((viewWidth - layoutSize.width) * 0.5f), y, layoutSize.width, layoutSize.height);
break;
if (lastLayoutItem) {
y += [layoutItem vSpacingWithLastLayoutItem:lastLayoutItem];
}
CGSize layoutSize = CGSizeCeil([layoutItem cellSize]);
// Ensure cell fits within view.
OWSAssert(layoutSize.width <= viewWidth);
layoutSize.width = MIN(viewWidth, layoutSize.width);
// All cell are "full width" and are responsible for aligning their own content.
CGRect itemFrame = CGRectMake(0, y, viewWidth, layoutSize.height);
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
UICollectionViewLayoutAttributes *itemAttributes =
[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
@ -143,11 +124,12 @@ NS_ASSUME_NONNULL_BEGIN
self.itemAttributesMap[@(row)] = itemAttributes;
contentBottom = itemFrame.origin.y + itemFrame.size.height;
y = contentBottom + vSpacing;
y = contentBottom;
row++;
lastLayoutItem = layoutItem;
}
contentBottom += vInset;
contentBottom += self.layoutInfo.contentMarginBottom;
self.contentSize = CGSizeMake(viewWidth, contentBottom);
}

View File

@ -1963,6 +1963,9 @@ NS_ASSUME_NONNULL_BEGIN
[prepareBlocks addObject:replyAssetLoader.prepareBlock];
}
// We don't need to configure ConversationLayoutInfo's view width in this case.
ConversationLayoutInfo *layoutInfo = [[ConversationLayoutInfo alloc] initWithThread:thread];
return [DebugUIMessagesSingleAction
actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
@ -1980,7 +1983,10 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(messageToQuote);
DDLogVerbose(@"%@ %@", self.logTag, label);
[DDLog flushLog];
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:messageToQuote isGroupThread:thread.isGroupThread transaction:transaction];
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:messageToQuote
isGroupThread:thread.isGroupThread
transaction:transaction
layoutInfo:layoutInfo];
quotedMessage = [[OWSQuotedReplyModel quotedReplyForConversationViewItem:viewItem transaction:transaction] buildQuotedMessage];
} else {
TSOutgoingMessage *_Nullable messageToQuote = [self createFakeOutgoingMessage:thread
@ -1994,7 +2000,10 @@ NS_ASSUME_NONNULL_BEGIN
transaction:transaction];
OWSAssert(messageToQuote);
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:messageToQuote isGroupThread:thread.isGroupThread transaction:transaction];
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:messageToQuote
isGroupThread:thread.isGroupThread
transaction:transaction
layoutInfo:layoutInfo];
quotedMessage = [[OWSQuotedReplyModel quotedReplyForConversationViewItem:viewItem transaction:transaction] buildQuotedMessage];
}
OWSAssert(quotedMessage);

View File

@ -479,7 +479,11 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
self.uiDatabaseConnection.read { transaction in
let message = galleryItem.message
let thread = message.thread(with: transaction)
fetchedItem = ConversationViewItem(interaction: message, isGroupThread: thread.isGroupThread(), transaction: transaction)
let conversationLayoutInfo = ConversationLayoutInfo(thread: thread)
fetchedItem = ConversationViewItem(interaction: message,
isGroupThread: thread.isGroupThread(),
transaction: transaction,
layoutInfo: conversationLayoutInfo)
}
guard let viewItem = fetchedItem else {

View File

@ -40,6 +40,8 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
var attachmentStream: TSAttachmentStream?
var messageBody: String?
var conversationLayoutInfo: ConversationLayoutInfo
private var contactShareViewHelper: ContactShareViewHelper
// MARK: Initializers
@ -50,13 +52,15 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
}
@objc
required init(viewItem: ConversationViewItem, message: TSMessage, mode: MessageMetadataViewMode) {
required init(viewItem: ConversationViewItem, message: TSMessage, thread: TSThread, mode: MessageMetadataViewMode) {
self.contactsManager = Environment.current().contactsManager
self.viewItem = viewItem
self.message = message
self.mode = mode
self.uiDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
self.contactShareViewHelper = ContactShareViewHelper(contactsManager: contactsManager)
self.conversationLayoutInfo = ConversationLayoutInfo(thread: thread)
super.init(nibName: nil, bundle: nil)
contactShareViewHelper.delegate = self
@ -83,6 +87,22 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
object: OWSPrimaryStorage.shared().dbNotificationObject)
}
override public func viewWillLayoutSubviews() {
Logger.debug("\(self.logTag) in \(#function)")
super.viewWillLayoutSubviews()
self.conversationLayoutInfo.viewWidth = self.view.width()
}
override public func viewDidLayoutSubviews() {
Logger.debug("\(self.logTag) in \(#function)")
super.viewDidLayoutSubviews()
self.conversationLayoutInfo.viewWidth = self.view.width()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
@ -331,7 +351,7 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
self.messageBubbleView = messageBubbleView
messageBubbleView.viewItem = viewItem
messageBubbleView.cellMediaCache = NSCache()
messageBubbleView.contentWidth = contentWidth()
messageBubbleView.layoutInfo = self.conversationLayoutInfo
messageBubbleView.alwaysShowBubbleTail = true
messageBubbleView.configureViews()
messageBubbleView.loadContent()
@ -574,10 +594,6 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
// MARK: - Message Bubble Layout
private func contentWidth() -> Int32 {
return Int32(round(self.view.width() - (2 * bubbleViewHMargin)))
}
private func updateMessageBubbleViewLayout() {
guard let messageBubbleView = messageBubbleView else {
return
@ -589,9 +605,7 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
return
}
messageBubbleView.contentWidth = contentWidth()
let messageBubbleSize = messageBubbleView.size(forContentWidth: contentWidth())
let messageBubbleSize = messageBubbleView.measureSize()
messageBubbleViewWidthLayoutConstraint.constant = messageBubbleSize.width
messageBubbleViewHeightLayoutConstraint.constant = messageBubbleSize.height
}

View File

@ -77,7 +77,10 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value);
// contents.
//
// NOTE: the margin values are inverted in RTL layouts.
//
// TODO: Remove this in favor of AppContext.isRTL()
- (BOOL)isRTL;
- (NSArray<NSLayoutConstraint *> *)autoPinLeadingAndTrailingToSuperviewMargin;
- (NSLayoutConstraint *)autoPinLeadingToSuperviewMargin;
- (NSLayoutConstraint *)autoPinLeadingToSuperviewMarginWithInset:(CGFloat)margin;
@ -150,6 +153,11 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height));
}
CG_INLINE CGSize CGSizeFloor(CGSize size)
{
return CGSizeMake((CGFloat)floor(size.width), (CGFloat)floor(size.height));
}
CG_INLINE CGSize CGSizeRound(CGSize size)
{
return CGSizeMake((CGFloat)round(size.width), (CGFloat)round(size.height));

View File

@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
createdByRemoteName:(nullable NSString *)remoteName
createdInExistingGroup:(BOOL)createdInExistingGroup;
- (NSString *)previewText;
@end
NS_ASSUME_NONNULL_END

View File

@ -50,6 +50,11 @@ NS_ASSUME_NONNULL_BEGIN
}
-(NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self previewText];
}
- (NSString *)previewText
{
if (self.createdInExistingGroup) {
OWSAssert(self.configurationIsEnabled && self.configurationDurationSeconds > 0);

View File

@ -27,6 +27,9 @@ typedef NS_ENUM(int32_t, TSErrorMessageType) {
@interface TSErrorMessage : TSMessage <OWSReadTracking>
@property (nonatomic, readonly) TSErrorMessageType errorType;
@property (nullable, nonatomic, readonly) NSString *recipientId;
- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp
inThread:(nullable TSThread *)thread
messageBody:(nullable NSString *)body
@ -70,8 +73,7 @@ typedef NS_ENUM(int32_t, TSErrorMessageType) {
+ (instancetype)nonblockingIdentityChangeInThread:(TSThread *)thread recipientId:(NSString *)recipientId;
@property (nonatomic, readonly) TSErrorMessageType errorType;
@property (nullable, nonatomic, readonly) NSString *recipientId;
- (NSString *)previewText;
@end

View File

@ -104,6 +104,11 @@ NSUInteger TSErrorMessageSchemaVersion = 1;
}
- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self previewText];
}
- (NSString *)previewText
{
switch (_errorType) {
case TSErrorMessageNoSession:

View File

@ -61,6 +61,8 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) {
expiresInSeconds:(uint32_t)expiresInSeconds
expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE;
- (NSString *)previewText;
@end
NS_ASSUME_NONNULL_END

View File

@ -112,6 +112,11 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
}
- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self previewText];
}
- (NSString *)previewText
{
switch (_messageType) {
case TSInfoMessageTypeSessionDidEnd:

View File

@ -35,6 +35,8 @@ typedef enum {
- (void)updateCallType:(RPRecentCallType)callType;
- (NSString *)previewText;
@end
NS_ASSUME_NONNULL_END

View File

@ -68,6 +68,11 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
}
- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction
{
return [self previewText];
}
- (NSString *)previewText
{
// We don't actually use the `transaction` but other sibling classes do.
switch (_callType) {

View File

@ -629,16 +629,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
[socketMessage didFailWithStatusCode:(NSInteger)responseStatus responseData:responseData error:error];
}
}
DDLogVerbose(@"%@ received WebSocket response: %llu, %zd, %@, %zd, %@, %d, %@.",
self.logTag,
(unsigned long long)requestId,
(NSInteger)responseStatus,
responseMessage,
responseData.length,
responseHeaders,
socketMessage != nil,
responseObject);
}
- (void)failAllPendingSocketMessagesIfNecessary

View File

@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
#endif
#define OWS_ABSTRACT_METHOD() OWSFail(@"Method needs to be implemented by subclasses.")
#define OWS_ABSTRACT_METHOD() OWSFail(@"%@ Method needs to be implemented by subclasses.", self.logTag)
#pragma mark - Singleton Asserts