Restore the date headers to the conversation view cells.
// FREEBIE
This commit is contained in:
parent
603a7d2639
commit
f7bd813c9f
|
@ -43,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
#pragma mark -
|
||||
|
||||
// TODO: Consider making this a protocol.
|
||||
@interface ConversationViewCell : UICollectionViewCell
|
||||
|
||||
@property (nonatomic, nullable, weak) id<ConversationViewCellDelegate> delegate;
|
||||
|
@ -51,10 +52,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@property (nonatomic) BOOL isCellVisible;
|
||||
|
||||
// If this is non-null, we should show the message date header.
|
||||
@property (nonatomic, nullable) NSAttributedString *messageDateHeaderText;
|
||||
|
||||
- (void)loadForDisplay;
|
||||
- (void)loadForDisplay:(int)contentWidth;
|
||||
|
||||
- (CGSize)cellSizeForViewWidth:(int)viewWidth contentWidth:(int)contentWidth;
|
||||
|
||||
|
|
|
@ -14,12 +14,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[super prepareForReuse];
|
||||
|
||||
self.viewItem = nil;
|
||||
self.messageDateHeaderText = nil;
|
||||
self.delegate = nil;
|
||||
self.isCellVisible = NO;
|
||||
}
|
||||
|
||||
- (void)loadForDisplay
|
||||
- (void)loadForDisplay:(int)contentWidth
|
||||
{
|
||||
OWSFail(@"%@ This method should be overridden.", self.logTag);
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (void)loadForDisplay
|
||||
- (void)loadForDisplay:(int)contentWidth
|
||||
{
|
||||
OWSAssert(self.viewItem);
|
||||
OWSAssert([self.viewItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]);
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
#import "AttachmentSharing.h"
|
||||
#import "AttachmentUploadView.h"
|
||||
#import "ConversationViewItem.h"
|
||||
#import "NSAttributedString+OWS.h"
|
||||
#import "OWSAudioMessageView.h"
|
||||
#import "OWSGenericAttachmentView.h"
|
||||
#import "UIColor+OWS.h"
|
||||
|
||||
//#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import "Signal-Swift.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import <JSQMessagesViewController/JSQMessagesTimestampFormatter.h>
|
||||
#import <JSQMessagesViewController/UIColor+JSQMessages.h>
|
||||
|
||||
//#import "OWSExpirationTimerView.h"
|
||||
|
@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@interface OWSMessageCell ()
|
||||
|
||||
// The text label is used so frequently that we always keep one around.
|
||||
@property (nonatomic) UIView *payloadView;
|
||||
@property (nonatomic) UILabel *dateHeaderLabel;
|
||||
@property (nonatomic) UILabel *textLabel;
|
||||
@property (nonatomic, nullable) UIImageView *bubbleImageView;
|
||||
@property (nonatomic, nullable) AttachmentUploadView *attachmentUploadView;
|
||||
|
@ -30,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic, nullable) AttachmentPointerView *attachmentPointerView;
|
||||
@property (nonatomic, nullable) OWSGenericAttachmentView *attachmentView;
|
||||
@property (nonatomic, nullable) OWSAudioMessageView *audioMessageView;
|
||||
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *dateHeaderConstraints;
|
||||
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *contentConstraints;
|
||||
|
||||
//@property (strong, nonatomic) OWSExpirationTimerView *expirationTimerView;
|
||||
|
@ -58,10 +61,19 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
self.contentView.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
self.payloadView = [UIView containerView];
|
||||
[self.contentView addSubview:self.payloadView];
|
||||
|
||||
self.dateHeaderLabel = [UILabel new];
|
||||
self.dateHeaderLabel.font = [UIFont ows_regularFontWithSize:16.f];
|
||||
self.dateHeaderLabel.textAlignment = NSTextAlignmentCenter;
|
||||
self.dateHeaderLabel.textColor = [UIColor lightGrayColor];
|
||||
[self.contentView addSubview:self.dateHeaderLabel];
|
||||
|
||||
self.bubbleImageView = [UIImageView new];
|
||||
self.bubbleImageView.layoutMargins = UIEdgeInsetsZero;
|
||||
self.bubbleImageView.userInteractionEnabled = NO;
|
||||
[self.contentView addSubview:self.bubbleImageView];
|
||||
[self.payloadView addSubview:self.bubbleImageView];
|
||||
[self.bubbleImageView autoPinToSuperviewEdges];
|
||||
|
||||
self.textLabel = [UILabel new];
|
||||
|
@ -75,6 +87,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// Hide these views by default.
|
||||
self.bubbleImageView.hidden = YES;
|
||||
self.textLabel.hidden = YES;
|
||||
self.dateHeaderLabel.hidden = YES;
|
||||
|
||||
[self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTop];
|
||||
[self.payloadView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.dateHeaderLabel];
|
||||
[self.payloadView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
[self.payloadView autoPinWidthToSuperview];
|
||||
|
||||
UITapGestureRecognizer *tap =
|
||||
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
||||
|
@ -122,7 +140,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return self.viewItem.contentSize;
|
||||
}
|
||||
|
||||
- (void)loadForDisplay
|
||||
- (void)loadForDisplay:(int)contentWidth
|
||||
{
|
||||
OWSAssert(self.viewItem);
|
||||
OWSAssert(self.viewItem.interaction);
|
||||
|
@ -135,6 +153,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
= isIncoming ? [self.bubbleFactory incoming] : [self.bubbleFactory outgoing];
|
||||
self.bubbleImageView.image = bubbleImageData.messageBubbleImage;
|
||||
|
||||
[self updateDateHeader:contentWidth];
|
||||
|
||||
switch (self.cellType) {
|
||||
case OWSMessageCellType_TextMessage:
|
||||
case OWSMessageCellType_OversizeTextMessage:
|
||||
|
@ -178,6 +198,80 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// });
|
||||
}
|
||||
|
||||
- (void)updateDateHeader:(int)contentWidth
|
||||
{
|
||||
static NSDateFormatter *dateHeaderDateFormatter = nil;
|
||||
static NSDateFormatter *dateHeaderTimeFormatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dateHeaderDateFormatter = [NSDateFormatter new];
|
||||
[dateHeaderDateFormatter setLocale:[NSLocale currentLocale]];
|
||||
[dateHeaderDateFormatter setDoesRelativeDateFormatting:YES];
|
||||
[dateHeaderDateFormatter setDateStyle:NSDateFormatterMediumStyle];
|
||||
[dateHeaderDateFormatter setTimeStyle:NSDateFormatterNoStyle];
|
||||
|
||||
dateHeaderTimeFormatter = [NSDateFormatter new];
|
||||
[dateHeaderTimeFormatter setLocale:[NSLocale currentLocale]];
|
||||
[dateHeaderTimeFormatter setDoesRelativeDateFormatting:YES];
|
||||
[dateHeaderTimeFormatter setDateStyle:NSDateFormatterNoStyle];
|
||||
[dateHeaderTimeFormatter setTimeStyle:NSDateFormatterShortStyle];
|
||||
});
|
||||
|
||||
if (self.viewItem.shouldShowDate) {
|
||||
NSDate *date = self.viewItem.interaction.dateForSorting;
|
||||
NSString *dateString = [dateHeaderDateFormatter stringFromDate:date];
|
||||
NSString *timeString = [dateHeaderTimeFormatter stringFromDate:date];
|
||||
|
||||
NSAttributedString *attributedText = [NSAttributedString new];
|
||||
attributedText = [attributedText rtlSafeAppend:dateString
|
||||
attributes:@{
|
||||
NSFontAttributeName : self.dateHeaderDateFont,
|
||||
NSForegroundColorAttributeName : [UIColor lightGrayColor],
|
||||
}
|
||||
referenceView:self];
|
||||
attributedText = [attributedText rtlSafeAppend:@" "
|
||||
attributes:@{
|
||||
NSFontAttributeName : self.dateHeaderDateFont,
|
||||
}
|
||||
referenceView:self];
|
||||
attributedText = [attributedText rtlSafeAppend:timeString
|
||||
attributes:@{
|
||||
NSFontAttributeName : self.dateHeaderTimeFont,
|
||||
NSForegroundColorAttributeName : [UIColor lightGrayColor],
|
||||
}
|
||||
referenceView:self];
|
||||
|
||||
self.dateHeaderLabel.attributedText = attributedText;
|
||||
self.dateHeaderLabel.hidden = NO;
|
||||
|
||||
self.dateHeaderConstraints = @[
|
||||
// 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:contentWidth],
|
||||
(self.isIncoming ? [self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]
|
||||
: [self.dateHeaderLabel autoPinEdgeToSuperviewEdge:ALEdgeTrailing]),
|
||||
[self.dateHeaderLabel autoSetDimension:ALDimensionHeight toSize:self.dateHeaderHeight],
|
||||
];
|
||||
} else {
|
||||
self.dateHeaderLabel.hidden = YES;
|
||||
self.dateHeaderConstraints = @[
|
||||
[self.dateHeaderLabel autoSetDimension:ALDimensionHeight toSize:0],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIFont *)dateHeaderDateFont
|
||||
{
|
||||
// TODO: Refine.
|
||||
return [UIFont boldSystemFontOfSize:12.0f];
|
||||
}
|
||||
|
||||
- (UIFont *)dateHeaderTimeFont
|
||||
{
|
||||
// TODO: Refine.
|
||||
return [UIFont systemFontOfSize:12.0f];
|
||||
}
|
||||
|
||||
- (void)loadForTextDisplay
|
||||
{
|
||||
self.bubbleImageView.hidden = NO;
|
||||
|
@ -313,7 +407,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssert(view);
|
||||
|
||||
view.userInteractionEnabled = NO;
|
||||
[self.contentView addSubview:view];
|
||||
[self.payloadView addSubview:view];
|
||||
self.contentConstraints = [view autoPinToSuperviewEdges];
|
||||
[self cropViewToBubbbleShape:view];
|
||||
if (self.isMediaBeingSent) {
|
||||
|
@ -378,7 +472,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
self.customView = [UIView new];
|
||||
self.customView.backgroundColor = [UIColor colorWithWhite:0.85f alpha:1.f];
|
||||
self.customView.userInteractionEnabled = NO;
|
||||
[self.contentView addSubview:self.customView];
|
||||
[self.payloadView addSubview:self.customView];
|
||||
self.contentConstraints = [self.customView autoPinToSuperviewEdges];
|
||||
[self cropViewToBubbbleShape:self.customView];
|
||||
}
|
||||
|
@ -390,6 +484,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
const int maxMessageWidth = (int)floor(contentWidth * 0.7f);
|
||||
|
||||
CGSize cellSize = CGSizeZero;
|
||||
switch (self.cellType) {
|
||||
case OWSMessageCellType_TextMessage:
|
||||
case OWSMessageCellType_OversizeTextMessage: {
|
||||
|
@ -401,9 +496,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
self.textLabel.text = self.textMessage;
|
||||
CGSize textSize = [self.textLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)];
|
||||
CGSize result = CGSizeMake((CGFloat)ceil(textSize.width + leftMargin + rightMargin),
|
||||
cellSize = CGSizeMake((CGFloat)ceil(textSize.width + leftMargin + rightMargin),
|
||||
(CGFloat)ceil(textSize.height + textVMargin * 2));
|
||||
return result;
|
||||
break;
|
||||
}
|
||||
case OWSMessageCellType_StillImage:
|
||||
case OWSMessageCellType_AnimatedImage:
|
||||
|
@ -422,18 +517,34 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
mediaWidth = (CGFloat)round(maxMediaHeight * self.contentSize.width / self.contentSize.height);
|
||||
mediaHeight = (CGFloat)round(maxMediaHeight);
|
||||
}
|
||||
CGSize result = CGSizeMake(mediaWidth, mediaHeight);
|
||||
return result;
|
||||
cellSize = CGSizeMake(mediaWidth, mediaHeight);
|
||||
break;
|
||||
}
|
||||
case OWSMessageCellType_Audio:
|
||||
return CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight);
|
||||
cellSize = CGSizeMake(maxMessageWidth, OWSAudioMessageView.bubbleHeight);
|
||||
break;
|
||||
case OWSMessageCellType_GenericAttachment:
|
||||
return CGSizeMake(maxMessageWidth, [OWSGenericAttachmentView bubbleHeight]);
|
||||
cellSize = CGSizeMake(maxMessageWidth, [OWSGenericAttachmentView bubbleHeight]);
|
||||
break;
|
||||
case OWSMessageCellType_DownloadingAttachment:
|
||||
return CGSizeMake(200, 90);
|
||||
cellSize = CGSizeMake(200, 90);
|
||||
break;
|
||||
}
|
||||
|
||||
return CGSizeMake(maxMessageWidth, maxMessageWidth);
|
||||
OWSAssert(cellSize.width > 0 && cellSize.height > 0);
|
||||
|
||||
cellSize.height += self.dateHeaderHeight;
|
||||
|
||||
return cellSize;
|
||||
}
|
||||
|
||||
- (CGFloat)dateHeaderHeight
|
||||
{
|
||||
if (self.viewItem.shouldShowDate) {
|
||||
return MAX(self.dateHeaderDateFont.lineHeight, self.dateHeaderTimeFont.lineHeight);
|
||||
} else {
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isIncoming
|
||||
|
@ -489,8 +600,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
[NSLayoutConstraint deactivateConstraints:self.contentConstraints];
|
||||
self.contentConstraints = nil;
|
||||
[NSLayoutConstraint deactivateConstraints:self.dateHeaderConstraints];
|
||||
self.dateHeaderConstraints = nil;
|
||||
|
||||
// The text label is used so frequently that we always keep one around.
|
||||
self.dateHeaderLabel.text = nil;
|
||||
self.dateHeaderLabel.hidden = YES;
|
||||
self.textLabel.text = nil;
|
||||
self.textLabel.hidden = YES;
|
||||
self.bubbleImageView.image = nil;
|
||||
|
|
|
@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (void)loadForDisplay
|
||||
- (void)loadForDisplay:(int)contentWidth
|
||||
{
|
||||
OWSAssert(self.viewItem);
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (void)loadForDisplay
|
||||
- (void)loadForDisplay:(int)contentWidth
|
||||
{
|
||||
OWSAssert(self.viewItem);
|
||||
OWSAssert([self.viewItem.interaction isKindOfClass:[TSUnreadIndicatorInteraction class]]);
|
||||
|
|
|
@ -156,6 +156,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
|
||||
@property (nonatomic, readonly) ConversationInputToolbar *inputToolbar;
|
||||
@property (nonatomic, readonly) ConversationCollectionView *collectionView;
|
||||
@property (nonatomic, readonly) ConversationViewLayout *layout;
|
||||
|
||||
@property (nonatomic) NSArray<ConversationViewItem *> *viewItems;
|
||||
@property (nonatomic) NSMutableDictionary<NSString *, ConversationViewItem *> *viewItemMap;
|
||||
|
@ -462,11 +463,12 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
|
||||
- (void)createContents
|
||||
{
|
||||
ConversationViewLayout *layout = [ConversationViewLayout new];
|
||||
layout.delegate = self;
|
||||
_layout = [ConversationViewLayout new];
|
||||
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.
|
||||
_collectionView = [[ConversationCollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
|
||||
_collectionView =
|
||||
[[ConversationCollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.layout];
|
||||
self.collectionView.layoutDelegate = self;
|
||||
self.collectionView.delegate = self;
|
||||
self.collectionView.dataSource = self;
|
||||
|
@ -3921,13 +3923,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
|
|||
cell.viewItem = viewItem;
|
||||
cell.delegate = self;
|
||||
|
||||
// TODO: Could we move this inside the cell base class?
|
||||
if (viewItem.shouldShowDate) {
|
||||
cell.messageDateHeaderText = [[JSQMessagesTimestampFormatter sharedFormatter]
|
||||
attributedTimestampForDate:viewItem.interaction.dateForSorting];
|
||||
}
|
||||
|
||||
[cell loadForDisplay];
|
||||
[cell loadForDisplay:self.layout.contentWidth];
|
||||
|
||||
return cell;
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ typedef NS_ENUM(NSInteger, ConversationViewLayoutAlignment) {
|
|||
|
||||
@property (nonatomic, weak) id<ConversationViewLayoutDelegate> delegate;
|
||||
|
||||
@property (nonatomic, readonly) int contentWidth;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// unnecessary layout pass.
|
||||
@property (nonatomic) BOOL hasLayout;
|
||||
|
||||
@property (nonatomic) int contentWidth;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
@ -85,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
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;
|
||||
|
||||
NSArray<id<ConversationViewLayoutItem>> *layoutItems = self.delegate.layoutItems;
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@interface NSAttributedString (OWS)
|
||||
|
||||
- (NSAttributedString *)rtlSafeAppend:(NSString *)text
|
||||
attributes:(NSDictionary *)attributes
|
||||
referenceView:(UIView *)referenceView;
|
||||
- (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string referenceView:(UIView *)referenceView;
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,6 +9,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@implementation NSAttributedString (OWS)
|
||||
|
||||
- (NSAttributedString *)rtlSafeAppend:(NSString *)text
|
||||
attributes:(NSDictionary *)attributes
|
||||
referenceView:(UIView *)referenceView
|
||||
{
|
||||
OWSAssert(text);
|
||||
OWSAssert(attributes);
|
||||
OWSAssert(referenceView);
|
||||
|
||||
NSAttributedString *substring = [[NSAttributedString alloc] initWithString:text attributes:attributes];
|
||||
return [self rtlSafeAppend:substring referenceView:referenceView];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string referenceView:(UIView *)referenceView
|
||||
{
|
||||
OWSAssert(string);
|
||||
|
|
Loading…
Reference in New Issue