From cbacda87cab401a3945f9f990265b1db26cd6300 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 25 Jun 2018 16:20:28 -0400 Subject: [PATCH 1/7] Introduce message cell footer view. --- Signal.xcodeproj/project.pbxproj | 14 +- .../Cells/OWSExpirableMessageView.h | 5 - .../Cells/OWSExpirationTimerView.h | 25 --- .../Cells/OWSExpirationTimerView.m | 192 ------------------ .../Cells/OWSMessageBubbleView.m | 38 ++++ .../ConversationView/Cells/OWSMessageCell.m | 5 +- .../Cells/OWSMessageFooterView.h | 17 ++ .../Cells/OWSMessageFooterView.m | 150 ++++++++++++++ .../ConversationView/ConversationViewItem.h | 1 + 9 files changed, 213 insertions(+), 234 deletions(-) delete mode 100644 Signal/src/ViewControllers/ConversationView/Cells/OWSExpirableMessageView.h delete mode 100644 Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.h delete mode 100644 Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.m create mode 100644 Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h create mode 100644 Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d5125205d..6ff0d9e95 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -203,7 +203,6 @@ 34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0721F8678AA0066283D /* ConversationViewLayout.m */; }; 34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0971F867BFC0066283D /* ConversationViewCell.m */; }; 34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */; }; - 34D1F0AC1F867BFC0066283D /* OWSExpirationTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */; }; 34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */; }; 34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */; }; 34D1F0B11F867BFC0066283D /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A81F867BFC0066283D /* OWSUnreadIndicatorCell.m */; }; @@ -225,6 +224,7 @@ 34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; }; 34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; }; 34D920E220DD39EA00D51158 /* ConversationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E120DD39E900D51158 /* ConversationStyle.swift */; }; + 34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E620E179C200D51158 /* OWSMessageFooterView.m */; }; 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 */; }; @@ -843,9 +843,6 @@ 34D1F0971F867BFC0066283D /* ConversationViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConversationViewCell.m; sourceTree = ""; }; 34D1F09A1F867BFC0066283D /* OWSContactOffersCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactOffersCell.h; sourceTree = ""; }; 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactOffersCell.m; sourceTree = ""; }; - 34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirableMessageView.h; sourceTree = ""; }; - 34D1F09D1F867BFC0066283D /* OWSExpirationTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirationTimerView.h; sourceTree = ""; }; - 34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSExpirationTimerView.m; sourceTree = ""; }; 34D1F0A11F867BFC0066283D /* OWSMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCell.h; sourceTree = ""; }; 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageCell.m; sourceTree = ""; }; 34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSystemMessageCell.h; sourceTree = ""; }; @@ -883,6 +880,8 @@ 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = ""; }; 34D913491F62D4A500722898 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = ""; }; 34D920E120DD39E900D51158 /* ConversationStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationStyle.swift; sourceTree = ""; }; + 34D920E520E179C100D51158 /* OWSMessageFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageFooterView.h; sourceTree = ""; }; + 34D920E620E179C200D51158 /* OWSMessageFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageFooterView.m; sourceTree = ""; }; 34D99C8A1F27B13B00D284D6 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSViewController.h; sourceTree = ""; }; 34D99C8B1F27B13B00D284D6 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSViewController.m; sourceTree = ""; }; 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = ""; }; @@ -1751,15 +1750,14 @@ 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */, 34CA63192097806E00E526A0 /* OWSContactShareView.h */, 34CA631A2097806E00E526A0 /* OWSContactShareView.m */, - 34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */, - 34D1F09D1F867BFC0066283D /* OWSExpirationTimerView.h */, - 34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */, 34D1F0B51F87F8850066283D /* OWSGenericAttachmentView.h */, 34D1F0B61F87F8850066283D /* OWSGenericAttachmentView.m */, 3496744B2076768600080B5F /* OWSMessageBubbleView.h */, 3496744C2076768700080B5F /* OWSMessageBubbleView.m */, 34D1F0A11F867BFC0066283D /* OWSMessageCell.h */, 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */, + 34D920E520E179C100D51158 /* OWSMessageFooterView.h */, + 34D920E620E179C200D51158 /* OWSMessageFooterView.m */, 34DBF000206BD5A400025978 /* OWSMessageTextView.h */, 34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */, 34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */, @@ -3232,7 +3230,6 @@ 3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */, 34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */, 34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */, - 34D1F0AC1F867BFC0066283D /* OWSExpirationTimerView.m in Sources */, 76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */, 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */, 45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */, @@ -3271,6 +3268,7 @@ 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */, 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */, 34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */, + 34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */, 348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */, 34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */, 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */, diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirableMessageView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirableMessageView.h deleted file mode 100644 index ad9727487..000000000 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirableMessageView.h +++ /dev/null @@ -1,5 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -// TODO: diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.h deleted file mode 100644 index 689fb1ab6..000000000 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -extern const CGFloat kExpirationTimerViewSize; - -@interface OWSExpirationTimerView : UIView - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; -- (instancetype)initWithExpiration:(uint64_t)expirationTimestamp - initialDurationSeconds:(uint32_t)initialDurationSeconds NS_DESIGNATED_INITIALIZER; - -- (void)ensureAnimations; - -- (void)clearAnimations; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.m deleted file mode 100644 index 4cd15ca41..000000000 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSExpirationTimerView.m +++ /dev/null @@ -1,192 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSExpirationTimerView.h" -#import "ConversationViewController.h" -#import "NSDate+OWS.h" -#import "OWSMath.h" -#import "UIColor+OWS.h" -#import "UIView+OWS.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -const CGFloat kExpirationTimerViewSize = 16.f; - -@interface OWSExpirationTimerView () - -@property (nonatomic) uint32_t initialDurationSeconds; -@property (nonatomic) uint64_t expirationTimestamp; - -@property (nonatomic, readonly) UIImageView *emptyHourglassImageView; -@property (nonatomic, readonly) UIImageView *fullHourglassImageView; -@property (nonatomic, nullable) CAGradientLayer *maskLayer; -@property (nonatomic, nullable) NSTimer *animationTimer; - -@end - -#pragma mark - - -@implementation OWSExpirationTimerView - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (instancetype)initWithExpiration:(uint64_t)expirationTimestamp initialDurationSeconds:(uint32_t)initialDurationSeconds -{ - self = [super initWithFrame:CGRectZero]; - if (!self) { - return self; - } - - self.expirationTimestamp = expirationTimestamp; - self.initialDurationSeconds = initialDurationSeconds; - - [self commonInit]; - - return self; -} - -- (void)commonInit -{ - self.clipsToBounds = YES; - - UIImage *hourglassEmptyImage = [[UIImage imageNamed:@"ic_hourglass_empty"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - UIImage *hourglassFullImage = [[UIImage imageNamed:@"ic_hourglass_full"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - _emptyHourglassImageView = [[UIImageView alloc] initWithImage:hourglassEmptyImage]; - self.emptyHourglassImageView.tintColor = [UIColor lightGrayColor]; - [self addSubview:self.emptyHourglassImageView]; - - _fullHourglassImageView = [[UIImageView alloc] initWithImage:hourglassFullImage]; - self.fullHourglassImageView.tintColor = [UIColor lightGrayColor]; - [self addSubview:self.fullHourglassImageView]; - - [self.emptyHourglassImageView autoPinHeightToSuperviewWithMargin:2.f]; - [self.emptyHourglassImageView autoHCenterInSuperview]; - [self.emptyHourglassImageView autoPinToSquareAspectRatio]; - [self.fullHourglassImageView autoPinHeightToSuperviewWithMargin:2.f]; - [self.fullHourglassImageView autoHCenterInSuperview]; - [self.fullHourglassImageView autoPinToSquareAspectRatio]; - [self autoSetDimension:ALDimensionWidth toSize:kExpirationTimerViewSize]; - [self autoSetDimension:ALDimensionHeight toSize:kExpirationTimerViewSize]; -} - -- (void)clearAnimations -{ - [self.layer removeAllAnimations]; - [self.maskLayer removeAllAnimations]; - [self.maskLayer removeFromSuperlayer]; - self.maskLayer = nil; - [self.fullHourglassImageView.layer.mask removeFromSuperlayer]; - self.fullHourglassImageView.layer.mask = nil; - self.layer.opacity = 1.f; - self.emptyHourglassImageView.hidden = YES; - self.fullHourglassImageView.hidden = YES; - [self.animationTimer invalidate]; - self.animationTimer = nil; -} - -- (void)setFrame:(CGRect)frame { - BOOL sizeDidChange = CGSizeEqualToSize(self.frame.size, frame.size); - [super setFrame:frame]; - if (sizeDidChange) { - [self ensureAnimations]; - } -} - -- (void)setBounds:(CGRect)bounds { - BOOL sizeDidChange = CGSizeEqualToSize(self.bounds.size, bounds.size); - [super setBounds:bounds]; - if (sizeDidChange) { - [self ensureAnimations]; - } -} - -- (void)ensureAnimations -{ - OWSAssertIsOnMainThread(); - - CGFloat secondsLeft = MAX(0, (self.expirationTimestamp - [NSDate ows_millisecondTimeStamp]) / 1000.f); - - [self clearAnimations]; - - const NSTimeInterval kBlinkAnimationDurationSeconds = 2; - - if (self.expirationTimestamp == 0) { - // If message hasn't started expiring yet, just show the full hourglass. - self.fullHourglassImageView.hidden = NO; - return; - } else if (secondsLeft <= kBlinkAnimationDurationSeconds + 0.1f) { - // If message has expired, just show the blinking empty hourglass. - self.emptyHourglassImageView.hidden = NO; - - // Flashing animation. - [UIView animateWithDuration:0.5f - delay:0.f - options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat - animations:^{ - self.layer.opacity = 0.f; - } - completion:^(BOOL finished) { - self.layer.opacity = 1.f; - }]; - return; - } - - self.emptyHourglassImageView.hidden = NO; - self.fullHourglassImageView.hidden = NO; - - CAGradientLayer *maskLayer = [CAGradientLayer new]; - maskLayer.anchorPoint = CGPointZero; - maskLayer.frame = self.fullHourglassImageView.bounds; - self.maskLayer = maskLayer; - self.fullHourglassImageView.layer.mask = maskLayer; - - // Blur the top of the mask a bit with gradient - maskLayer.colors = @[ (id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor ]; - maskLayer.startPoint = CGPointMake(0.5f, 0.f); - // Use a mask that is 20% tall to soften the edge of the animation. - const CGFloat kMaskEdgeFraction = 0.2f; - maskLayer.endPoint = CGPointMake(0.5f, kMaskEdgeFraction); - - NSTimeInterval timeUntilFlashing = MAX(0, secondsLeft - kBlinkAnimationDurationSeconds); - - if (self.initialDurationSeconds == 0) { - OWSFail(@"initialDurationSeconds was unexpectedly 0"); - return; - } - - CGFloat ratioRemaining = (CGFloat)secondsLeft / (CGFloat)self.initialDurationSeconds; - CGFloat ratioComplete = CGFloatClamp((CGFloat)1.0 - ratioRemaining, 0, 1.0); - CGPoint startPosition = CGPointMake(0, self.fullHourglassImageView.height * ratioComplete); - - // We offset the bottom slightly to make sure the duration of the perceived animation is correct. - // We're accounting for: - // - the bottom pixel of the two images is the outline of the hourglass. Because the outline is identical in the full vs empty hourglass this wouldn't be perceptible. - // - the top pixel is not visible due to our softening gradient layer. - CGPoint endPosition = CGPointMake(0, self.fullHourglassImageView.height - 2); - - maskLayer.position = startPosition; - [CATransaction begin]; - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; - animation.duration = timeUntilFlashing; - animation.fromValue = [NSValue valueWithCGPoint:startPosition]; - animation.toValue = [NSValue valueWithCGPoint:endPosition]; - [maskLayer addAnimation:animation forKey:@"slideAnimation"]; - maskLayer.position = endPosition; // don't snap back - [CATransaction commit]; - - self.animationTimer = [NSTimer weakScheduledTimerWithTimeInterval:timeUntilFlashing - target:self - selector:@selector(ensureAnimations) - userInfo:nil - repeats:NO]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 87b47b9d4..83ea4db32 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -10,6 +10,7 @@ #import "OWSBubbleView.h" #import "OWSContactShareView.h" #import "OWSGenericAttachmentView.h" +#import "OWSMessageFooterView.h" #import "OWSMessageTextView.h" #import "OWSQuotedMessageView.h" #import "Signal-Swift.h" @@ -36,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) NSMutableArray *viewConstraints; +@property (nonatomic) OWSMessageFooterView *footerView; + @end @implementation OWSMessageBubbleView @@ -73,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN self.bodyTextView.dataDetectorTypes = (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent); self.bodyTextView.hidden = YES; + + self.footerView = [OWSMessageFooterView new]; } - (OWSMessageTextView *)newTextView @@ -436,6 +441,20 @@ NS_ASSUME_NONNULL_BEGIN bottomMargin = textInsets.bottom; } + OWSMessageFooterView *footerView = self.footerView; + [footerView configureWithConversationViewItem:self.viewItem]; + if (self.footerView) { + [self.bubbleView addSubview:self.footerView]; + [self.viewConstraints addObjectsFromArray:@[ + [tapForMoreLabel autoPinLeadingToSuperviewMarginWithInset:textInsets.leading], + [tapForMoreLabel autoPinTrailingToSuperviewMarginWithInset:textInsets.trailing], + [tapForMoreLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview], + [tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight], + ]]; + lastSubview = tapForMoreLabel; + bottomMargin = textInsets.bottom; + } + OWSAssert(lastSubview); [self.viewConstraints addObjectsFromArray:@[ [lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:bottomMargin], @@ -1007,11 +1026,23 @@ NS_ASSUME_NONNULL_BEGIN cellSize.height += self.tapForMoreHeight; } + if (self.hasFooter) { + CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem]; + cellSize.width = MAX(cellSize.width, footerSize.width); + cellSize.height += self.footerVSpacing + footerSize.height; + } + cellSize = CGSizeCeil(cellSize); return cellSize; } +- (BOOL)hasFooter +{ + // TODO: + return YES; +} + - (UIFont *)tapForMoreFont { return UIFont.ows_dynamicTypeCaption1Font; @@ -1022,6 +1053,11 @@ NS_ASSUME_NONNULL_BEGIN return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25); } +- (CGFloat)footerVSpacing +{ + return 10.f; +} + #pragma mark - - (UIColor *)bodyTextColor @@ -1082,6 +1118,8 @@ NS_ASSUME_NONNULL_BEGIN [self.quotedMessageView removeFromSuperview]; self.quotedMessageView = nil; + + [self.footerView removeFromSuperview]; } #pragma mark - Gestures diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 176a41725..26a4a2be9 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -4,7 +4,6 @@ #import "OWSMessageCell.h" #import "OWSContactAvatarBuilder.h" -#import "OWSExpirationTimerView.h" #import "OWSMessageBubbleView.h" #import "Signal-Swift.h" @@ -24,10 +23,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) OWSMessageBubbleView *messageBubbleView; @property (nonatomic) UILabel *dateHeaderLabel; -@property (nonatomic) UIView *footerView; @property (nonatomic) AvatarImageView *avatarView; -@property (nonatomic) UILabel *footerLabel; -@property (nonatomic, nullable) OWSExpirationTimerView *expirationTimerView; +@property (nonatomic) OWSMessageFooterView *footerView2; @property (nonatomic, nullable) NSMutableArray *viewConstraints; @property (nonatomic) BOOL isPresentingMenuController; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h new file mode 100644 index 000000000..e837d6ad8 --- /dev/null +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h @@ -0,0 +1,17 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +@class ConversationViewItem; + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSMessageFooterView : UIView + +- (void)configureWithConversationViewItem:(ConversationViewItem *)viewItem; + +- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m new file mode 100644 index 000000000..a89e34c70 --- /dev/null +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -0,0 +1,150 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "OWSMessageFooterView.h" +#import "DateUtil.h" +#import "OWSExpirationTimerView.h" +#import "Signal-Swift.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSMessageFooterView () + +@property (nonatomic) UILabel *timestampLabel; +@property (nonatomic) UILabel *statusLabel; +@property (nonatomic) UIView *statusIndicatorView; + +@end + +@implementation OWSMessageFooterView + +// `[UIView init]` invokes `[self initWithFrame:...]`. +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + [self commontInit]; + } + + return self; +} + +- (void)commontInit +{ + // Ensure only called once. + OWSAssert(!self.timestampLabel); + + self.layoutMargins = UIEdgeInsetsZero; + + self.timestampLabel = [UILabel new]; + // TODO: Color + self.timestampLabel.textColor = [UIColor lightGrayColor]; + + self.statusLabel = [UILabel new]; + // TODO: Color + self.statusLabel.textColor = [UIColor lightGrayColor]; + + self.statusIndicatorView = [UIView new]; + [self.statusIndicatorView autoSetDimension:ALDimensionWidth toSize:self.statusIndicatorSize]; + [self.statusIndicatorView autoSetDimension:ALDimensionHeight toSize:self.statusIndicatorSize]; + self.statusIndicatorView.layer.cornerRadius = self.statusIndicatorSize * 0.5f; + + // TODO: Review constant with Myles.0 + UIStackView *statusStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + self.statusLabel, + self.statusIndicatorView, + ]]; + statusStackView.axis = UILayoutConstraintAxisHorizontal; + statusStackView.spacing = self.hSpacing; + + [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]; + [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; + [self.timestampLabel autoVCenterInSuperview]; + [statusStackView autoVCenterInSuperview]; + [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; + [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom + withInset:0 + relation:NSLayoutRelationGreaterThanOrEqual]; + [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; + [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; + [statusStackView autoPinEdge:ALEdgeLeading + toEdge:ALEdgeTrailing + ofView:self.timestampLabel + withOffset:self.hSpacing + relation:NSLayoutRelationGreaterThanOrEqual]; +} + +- (void)configureFonts +{ + self.timestampLabel.font = UIFont.ows_dynamicTypeCaption2Font; + self.statusLabel.font = UIFont.ows_dynamicTypeCaption2Font; +} + +- (CGFloat)statusIndicatorSize +{ + // TODO: Review constant. + return 20.f; +} + +- (CGFloat)hSpacing +{ + // TODO: Review constant. + return 10.f; +} + +#pragma mark - Load + +- (void)configureWithConversationViewItem:(ConversationViewItem *)viewItem +{ + OWSAssert(viewItem); + + [self configureLabelsWithConversationViewItem:viewItem]; + ; + + // TODO: + self.statusIndicatorView.backgroundColor = [UIColor ows_materialBlueColor]; +} + +- (void)configureLabelsWithConversationViewItem:(ConversationViewItem *)viewItem +{ + OWSAssert(viewItem); + + [self configureFonts]; + + // TODO: Correct text. + self.timestampLabel.text = + [DateUtil formatPastTimestampRelativeToNow:viewItem.interaction.timestamp isRTL:CurrentAppContext().isRTL]; + self.statusLabel.text = [self messageStatusTextForConversationViewItem:viewItem]; +} + +- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem +{ + OWSAssert(viewItem); + + [self configureLabelsWithConversationViewItem:viewItem]; + ; + + CGSize result = CGSizeZero; + result.height + = MAX(self.timestampLabel.font.lineHeight, MAX(self.statusLabel.font.lineHeight, self.statusIndicatorSize)); + result.width = ([self.timestampLabel sizeThatFits:CGSizeZero].width + + [self.statusLabel sizeThatFits:CGSizeZero].width + self.statusIndicatorSize + self.hSpacing * 2.f); + return CGSizeCeil(result); +} + +- (nullable NSString *)messageStatusTextForConversationViewItem:(ConversationViewItem *)viewItem +{ + OWSAssert(viewItem); + if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) { + return nil; + } + + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction; + NSString *statusMessage = + [MessageRecipientStatusUtils receiptMessageWithOutgoingMessage:outgoingMessage referenceView:self]; + return statusMessage; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 6293589ee..4c6694124 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -55,6 +55,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic, readonly) BOOL hasQuotedText; @property (nonatomic) BOOL shouldShowDate; +// TODO: Consider renaming to shouldHideFooter. @property (nonatomic) BOOL shouldHideRecipientStatus; // Used to suppress "group sender" avatars. @property (nonatomic) BOOL shouldHideAvatar; From a769499f56c09478294d658ba5085299d939131b Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 25 Jun 2018 17:50:41 -0400 Subject: [PATCH 2/7] Remove overzealous assert in search finder. --- SignalServiceKit/src/Storage/FullTextSearchFinder.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift index 13557e32f..39e935d30 100644 --- a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift +++ b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift @@ -211,7 +211,8 @@ public class FullTextSearchFinder: NSObject { var oversizeText: String? dbConnection.read({ (transaction) in guard let attachment = message.attachment(with: transaction) else { - owsFail("Could not load attachment for search indexing.") + // This can happen during the initial save of incoming messages. + Logger.warn("Could not load attachment for search indexing.") return } guard let attachmentStream = attachment as? TSAttachmentStream else { From f363a196f3bc1949e7c20d7ebcc0e6dfb7812fd4 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 25 Jun 2018 18:06:08 -0400 Subject: [PATCH 3/7] Introduce message cell footer view. --- .../Cells/OWSMessageBubbleView.m | 194 +++++++++--------- .../ConversationView/Cells/OWSMessageCell.m | 161 +-------------- .../Cells/OWSMessageFooterView.m | 3 +- .../ConversationView/ConversationStyle.swift | 41 +--- .../Attachments/OWSAttachmentsProcessor.m | 2 + .../src/Messages/OWSMessageManager.m | 6 + 6 files changed, 110 insertions(+), 297 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 83ea4db32..089838cbe 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) OWSBubbleView *bubbleView; +@property (nonatomic) UIStackView *stackView; + @property (nonatomic) OWSMessageTextView *bodyTextView; @property (nonatomic, nullable) UIView *quotedMessageView; @@ -71,6 +73,12 @@ NS_ASSUME_NONNULL_BEGIN [self addSubview:self.bubbleView]; [self.bubbleView autoPinEdgesToSuperviewEdges]; + self.stackView = [UIStackView new]; + self.stackView.axis = UILayoutConstraintAxisVertical; + self.stackView.alignment = UIStackViewAlignmentFill; + [self addSubview:self.stackView]; + [self.stackView autoPinEdgesToSuperviewEdges]; + self.bodyTextView = [self newTextView]; // Setting dataDetectorTypes is expensive. Do it just once. self.bodyTextView.dataDetectorTypes @@ -248,6 +256,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]); CGSize quotedMessageContentSize = [self quotedMessageSize]; + // TODO: CGSize bodyMediaContentSize = [self bodyMediaSize]; CGSize bodyTextContentSize = [self bodyTextSizeWithIncludeMargins:NO]; @@ -260,12 +269,9 @@ NS_ASSUME_NONNULL_BEGIN self.bubbleView.bubbleColor = nil; } - UIView *_Nullable lastSubview = nil; - CGFloat bottomMargin = 0; + // CGFloat bottomMargin = 0; if (self.isQuotedReply) { - OWSAssert(!lastSubview); - BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class]; DisplayableText *_Nullable displayableQuotedText = (self.viewItem.hasQuotedText ? self.viewItem.displayableQuotedText : nil); @@ -278,23 +284,11 @@ NS_ASSUME_NONNULL_BEGIN self.quotedMessageView = quotedMessageView; [quotedMessageView createContents]; - [self.bubbleView addSubview:quotedMessageView]; - - [self.viewConstraints addObjectsFromArray:@[ - [quotedMessageView autoPinLeadingToSuperviewMargin], - [quotedMessageView autoPinTrailingToSuperviewMargin], - ]]; + [self.stackView addArrangedSubview:quotedMessageView]; [self.viewConstraints addObject:[quotedMessageView autoSetDimension:ALDimensionHeight toSize:quotedMessageContentSize.height]]; - if (lastSubview) { - [self.viewConstraints - addObject:[quotedMessageView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview]]; - } else { - [self.viewConstraints addObject:[quotedMessageView autoPinEdgeToSuperviewEdge:ALEdgeTop]]; - } - lastSubview = quotedMessageView; - bottomMargin = 0; + // bottomMargin = 0; [self.bubbleView addPartnerView:quotedMessageView.boundsStrokeView]; } @@ -349,34 +343,28 @@ NS_ASSUME_NONNULL_BEGIN bodyMediaView.layer.opacity = 0.75f; } - [self.bubbleView addSubview:bodyMediaView]; - // This layout can lead to extreme cropping of media content, - // e.g. a very tall portrait image + long caption. The media - // view will have "max width", so the image will be cropped to - // roughly a square. - // TODO: Myles is considering alternatives. - [self.viewConstraints addObjectsFromArray:@[ - [bodyMediaView autoPinLeadingToSuperviewMarginWithInset:0], - [bodyMediaView autoPinTrailingToSuperviewMarginWithInset:0], - ]]; - // We need constraints to control the vertical sizing of the media view, but we use - // lower priority so that when a message only contains media it uses the exact bounds of - // the message view. - [NSLayoutConstraint - autoSetPriority:UILayoutPriorityDefaultLow - forConstraints:^{ - [self.viewConstraints - addObject:[bodyMediaView autoSetDimension:ALDimensionHeight toSize:bodyMediaContentSize.height]]; - }]; - - if (lastSubview) { - [self.viewConstraints - addObject:[bodyMediaView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:0]]; - } else { - [self.viewConstraints addObject:[bodyMediaView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0]]; - } - lastSubview = bodyMediaView; - bottomMargin = 0; + [self.stackView addArrangedSubview:bodyMediaView]; + // // This layout can lead to extreme cropping of media content, + // // e.g. a very tall portrait image + long caption. The media + // // view will have "max width", so the image will be cropped to + // // roughly a square. + // // TODO: Myles is considering alternatives. + // [self.viewConstraints addObjectsFromArray:@[ + // [bodyMediaView autoPinLeadingToSuperviewMarginWithInset:0], + // [bodyMediaView autoPinTrailingToSuperviewMarginWithInset:0], + // ]]; + // // We need constraints to control the vertical sizing of the media view, but we use + // // lower priority so that when a message only contains media it uses the exact bounds of + // // the message view. + // [NSLayoutConstraint + // autoSetPriority:UILayoutPriorityDefaultLow + // forConstraints:^{ + // [self.viewConstraints + // addObject:[bodyMediaView autoSetDimension:ALDimensionHeight + // toSize:bodyMediaContentSize.height]]; + // }]; + + // bottomMargin = 0; BOOL shouldStrokeMediaView = ([bodyMediaView isKindOfClass:[UIImageView class]] || [bodyMediaView isKindOfClass:[OWSContactShareView class]]); @@ -395,70 +383,73 @@ NS_ASSUME_NONNULL_BEGIN } } - OWSDirectionalEdgeInsets *textInsets = self.conversationStyle.textInsets; - OWSAssert(textInsets); - OWSMessageTextView *_Nullable bodyTextView = nil; + UIStackView *_Nullable textStackView = [UIStackView new]; + // We render malformed messages as "empty text" messages, // so create a text view if there is no body media view. if (self.hasBodyText || !bodyMediaView) { bodyTextView = [self configureBodyTextView]; } if (bodyTextView) { - [self.bubbleView addSubview: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:@[ - [bodyTextView autoPinLeadingToSuperviewMarginWithInset:textInsets.leading], - [bodyTextView autoPinTrailingToSuperviewMarginWithInset:textInsets.trailing], - [bodyTextView autoSetDimension:ALDimensionWidth toSize:bodyTextContentSize.width], + // [bodyTextView autoSetDimension:ALDimensionWidth toSize:bodyTextContentSize.width + // relation:NSLayoutRelationLessThanOrEqual], [bodyTextView autoSetDimension:ALDimensionHeight toSize:bodyTextContentSize.height], ]]; - if (lastSubview) { - [self.viewConstraints addObject:[bodyTextView autoPinEdge:ALEdgeTop - toEdge:ALEdgeBottom - ofView:lastSubview - withOffset:textInsets.top]]; - } else { - [self.viewConstraints - addObject:[bodyTextView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:textInsets.top]]; + UIView *_Nullable tapForMoreLabel = [self createTapForMoreLabelIfNecessary]; + if (tapForMoreLabel) { + [textStackView addArrangedSubview:tapForMoreLabel]; + [self.viewConstraints addObjectsFromArray:@[ + // [tapForMoreLabel autoPinEdge:ALEdgeTop + // toEdge:ALEdgeBottom ofView:lastSubview], + [tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight], + ]]; } - lastSubview = bodyTextView; - bottomMargin = textInsets.bottom; } - UIView *_Nullable tapForMoreLabel = [self createTapForMoreLabelIfNecessary]; - if (tapForMoreLabel) { - OWSAssert(lastSubview); - OWSAssert(lastSubview == bodyTextView); - [self.bubbleView addSubview:tapForMoreLabel]; + OWSMessageFooterView *footerView = self.footerView; + [footerView configureWithConversationViewItem:self.viewItem]; + if (textStackView) { + [textStackView addArrangedSubview:self.footerView]; + } else if (bodyMediaView) { + [bodyMediaView addSubview:footerView]; + + bodyMediaView.layoutMargins = UIEdgeInsetsZero; [self.viewConstraints addObjectsFromArray:@[ - [tapForMoreLabel autoPinLeadingToSuperviewMarginWithInset:textInsets.leading], - [tapForMoreLabel autoPinTrailingToSuperviewMarginWithInset:textInsets.trailing], - [tapForMoreLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview], - [tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight], + [footerView autoPinLeadingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal], + [footerView autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal], + [footerView autoPinBottomToSuperviewMarginWithInset:self.conversationStyle.textInsetBottom], ]]; - lastSubview = tapForMoreLabel; - bottomMargin = textInsets.bottom; + // TODO: Drop shadow. + } else { + // Display footer over media. + OWSFail(@"%@ could not display footer.", self.logTag); } - OWSMessageFooterView *footerView = self.footerView; - [footerView configureWithConversationViewItem:self.viewItem]; - if (self.footerView) { - [self.bubbleView addSubview:self.footerView]; + // TODO: Should we do this for media content too? + if (textStackView) { + CGSize bubbleSize = [self measureSize]; [self.viewConstraints addObjectsFromArray:@[ - [tapForMoreLabel autoPinLeadingToSuperviewMarginWithInset:textInsets.leading], - [tapForMoreLabel autoPinTrailingToSuperviewMarginWithInset:textInsets.trailing], - [tapForMoreLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview], - [tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight], + [self autoSetDimension:ALDimensionWidth toSize:bubbleSize.width relation:NSLayoutRelationLessThanOrEqual], + // [bodyTextView autoSetDimension:ALDimensionHeight + // toSize:bodyTextContentSize.height], ]]; - lastSubview = tapForMoreLabel; - bottomMargin = textInsets.bottom; } - - OWSAssert(lastSubview); - [self.viewConstraints addObjectsFromArray:@[ - [lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:bottomMargin], - ]]; } // We now eagerly create our view hierarchy (to do this exactly once per cell usage) @@ -498,6 +489,11 @@ NS_ASSUME_NONNULL_BEGIN return cellMedia; } +- (CGFloat)textViewVSpacing +{ + return 5.f; +} + #pragma mark - Load / Unload - (void)loadContent @@ -881,10 +877,7 @@ NS_ASSUME_NONNULL_BEGIN return CGSizeZero; } - OWSDirectionalEdgeInsets *textInsets = self.conversationStyle.textInsets; - OWSAssert(textInsets); - - CGFloat hMargins = textInsets.leading + textInsets.trailing; + CGFloat hMargins = self.conversationStyle.textInsetHorizontal * 2; const int maxTextWidth = (int)floor(self.conversationStyle.maxMessageWidth - hMargins); @@ -895,7 +888,7 @@ NS_ASSUME_NONNULL_BEGIN if (includeMargins) { result.width += hMargins; - result.height += textInsets.top + textInsets.bottom; + result.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom); } return CGSizeCeil(result); @@ -1023,13 +1016,15 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(cellSize.width > 0 && cellSize.height > 0); if (self.hasTapForMore) { - cellSize.height += self.tapForMoreHeight; + cellSize.height += self.tapForMoreHeight + self.textViewVSpacing; } - if (self.hasFooter) { + // TODO: Update this to reflect generic attachment, downloading attachments and + // contact shares. + if (self.hasFooter && self.hasBodyText) { CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem]; cellSize.width = MAX(cellSize.width, footerSize.width); - cellSize.height += self.footerVSpacing + footerSize.height; + cellSize.height += self.textViewVSpacing + footerSize.height; } cellSize = CGSizeCeil(cellSize); @@ -1053,11 +1048,6 @@ NS_ASSUME_NONNULL_BEGIN return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25); } -- (CGFloat)footerVSpacing -{ - return 10.f; -} - #pragma mark - - (UIColor *)bodyTextColor diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 26a4a2be9..0abe0763f 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -24,7 +24,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) OWSMessageBubbleView *messageBubbleView; @property (nonatomic) UILabel *dateHeaderLabel; @property (nonatomic) AvatarImageView *avatarView; -@property (nonatomic) OWSMessageFooterView *footerView2; @property (nonatomic, nullable) NSMutableArray *viewConstraints; @property (nonatomic) BOOL isPresentingMenuController; @@ -56,20 +55,12 @@ NS_ASSUME_NONNULL_BEGIN self.messageBubbleView = [OWSMessageBubbleView new]; [self.contentView addSubview:self.messageBubbleView]; - self.footerView = [UIView containerView]; - [self.contentView addSubview:self.footerView]; - self.dateHeaderLabel = [UILabel new]; self.dateHeaderLabel.font = self.dateHeaderDateFont; self.dateHeaderLabel.textAlignment = NSTextAlignmentCenter; self.dateHeaderLabel.textColor = [UIColor lightGrayColor]; [self.contentView addSubview:self.dateHeaderLabel]; - self.footerLabel = [UILabel new]; - self.footerLabel.font = UIFont.ows_dynamicTypeCaption2Font; - self.footerLabel.textColor = [UIColor lightGrayColor]; - [self.footerView addSubview:self.footerLabel]; - self.avatarView = [[AvatarImageView alloc] init]; [self.contentView addSubview:self.avatarView]; [self.avatarView autoSetDimension:ALDimensionWidth toSize:self.avatarSize]; @@ -77,12 +68,10 @@ NS_ASSUME_NONNULL_BEGIN // Hide these views by default. self.dateHeaderLabel.hidden = YES; - self.footerLabel.hidden = YES; self.avatarView.hidden = YES; [self.messageBubbleView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.dateHeaderLabel]; - - [self.footerView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; + [self.messageBubbleView autoPinBottomToSuperviewMarginWithInset:0]; self.contentView.userInteractionEnabled = YES; @@ -158,7 +147,6 @@ NS_ASSUME_NONNULL_BEGIN // Update label fonts to honor dynamic type size. self.dateHeaderLabel.font = self.dateHeaderDateFont; - self.footerLabel.font = UIFont.ows_dynamicTypeCaption2Font; if (self.isIncoming) { [self.viewConstraints addObjectsFromArray:@[ @@ -179,7 +167,6 @@ NS_ASSUME_NONNULL_BEGIN } [self updateDateHeader]; - [self updateFooter]; if ([self updateAvatarView]) { CGFloat avatarBottomMargin = round(self.conversationStyle.lastTextLineAxis - self.avatarSize * 0.5f); @@ -275,132 +262,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (BOOL)shouldShowFooter -{ - BOOL shouldShowFooter = NO; - - if (self.message.shouldStartExpireTimer) { - shouldShowFooter = YES; - } else if (self.isOutgoing) { - shouldShowFooter = !self.viewItem.shouldHideRecipientStatus; - } else if (self.viewItem.isGroupThread) { - shouldShowFooter = YES; - } else { - shouldShowFooter = NO; - } - - return shouldShowFooter; -} - -- (CGFloat)footerHeight -{ - if (!self.shouldShowFooter) { - return 0.f; - } - - return ceil(MAX(kExpirationTimerViewSize, self.footerLabel.font.lineHeight)); -} - -- (CGFloat)footerVSpacing -{ - return 0.f; -} - -- (void)updateFooter -{ - OWSAssert(self.conversationStyle); - OWSAssert(self.viewItem.interaction.interactionType == OWSInteractionType_IncomingMessage - || self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage); - - TSMessage *message = self.message; - BOOL hasExpirationTimer = message.shouldStartExpireTimer; - NSAttributedString *attributedText = nil; - if (self.isOutgoing) { - if (!self.viewItem.shouldHideRecipientStatus || hasExpirationTimer) { - TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message; - NSString *statusMessage = - [MessageRecipientStatusUtils receiptMessageWithOutgoingMessage:outgoingMessage referenceView:self]; - attributedText = [[NSAttributedString alloc] initWithString:statusMessage attributes:@{}]; - } - } else if (self.viewItem.isGroupThread) { - TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction; - attributedText = [self.delegate attributedContactOrProfileNameForPhoneIdentifier:incomingMessage.authorId]; - } - - if (!hasExpirationTimer && - !attributedText) { - self.footerLabel.hidden = YES; - [self.viewConstraints addObjectsFromArray:@[ - [self.footerView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.messageBubbleView], - [self.footerView autoSetDimension:ALDimensionHeight toSize:0], - ]]; - return; - } - - [self.viewConstraints addObjectsFromArray:@[ - (self.isIncoming - ? [self.footerView autoPinLeadingToSuperviewMarginWithInset:self.conversationStyle.gutterLeading] - : [self.footerView autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.gutterTrailing]), - ]]; - - [self.viewConstraints addObject:[self.footerView autoPinEdge:ALEdgeTop - toEdge:ALEdgeBottom - ofView:self.messageBubbleView - withOffset:self.footerVSpacing]]; - - if (hasExpirationTimer) { - uint64_t expirationTimestamp = message.expiresAt; - uint32_t expiresInSeconds = message.expiresInSeconds; - self.expirationTimerView = [[OWSExpirationTimerView alloc] initWithExpiration:expirationTimestamp - initialDurationSeconds:expiresInSeconds]; - [self.footerView addSubview:self.expirationTimerView]; - } - if (attributedText) { - self.footerLabel.attributedText = attributedText; - self.footerLabel.hidden = NO; - } - - // Footer labels can extend past the message bubble, but - // 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.conversationStyle.maxFooterWidth; - if (hasExpirationTimer && - attributedText) { - [self.viewConstraints addObjectsFromArray:@[ - [self.expirationTimerView autoVCenterInSuperview], - [self.footerLabel autoVCenterInSuperview], - (self.isIncoming ? [self.expirationTimerView autoPinLeadingToSuperviewMargin] - : [self.expirationTimerView autoPinTrailingToSuperviewMargin]), - (self.isIncoming ? [self.footerLabel autoPinLeadingToTrailingEdgeOfView:self.expirationTimerView] - : [self.footerLabel autoPinTrailingToLeadingEdgeOfView:self.expirationTimerView]), - [self.footerLabel autoSetDimension:ALDimensionWidth - toSize:maxFooterLabelWidth - relation:NSLayoutRelationLessThanOrEqual], - [self.footerView autoSetDimension:ALDimensionHeight toSize:self.footerHeight], - ]]; - } else if (hasExpirationTimer) { - [self.viewConstraints addObjectsFromArray:@[ - [self.expirationTimerView autoVCenterInSuperview], - (self.isIncoming ? [self.expirationTimerView autoPinLeadingToSuperviewMargin] - : [self.expirationTimerView autoPinTrailingToSuperviewMargin]), - [self.footerView autoSetDimension:ALDimensionHeight toSize:self.footerHeight], - ]]; - } else if (attributedText) { - [self.viewConstraints addObjectsFromArray:@[ - [self.footerLabel autoVCenterInSuperview], - (self.isIncoming ? [self.footerLabel autoPinLeadingToSuperviewMargin] - : [self.footerLabel autoPinTrailingToSuperviewMargin]), - [self.footerView autoSetDimension:ALDimensionHeight toSize:self.footerHeight], - [self.footerLabel autoSetDimension:ALDimensionWidth - toSize:maxFooterLabelWidth - relation:NSLayoutRelationLessThanOrEqual], - ]]; - } else { - OWSFail(@"%@ Cell unexpectedly has neither expiration timer nor footer text.", self.logTag); - } -} - - (UIFont *)dateHeaderDateFont { return UIFont.ows_dynamicTypeCaption1Font.ows_mediumWeight; @@ -498,10 +359,6 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(cellSize.width > 0 && cellSize.height > 0); cellSize.height += self.dateHeaderHeight; - if (self.shouldShowFooter) { - cellSize.height += self.footerVSpacing; - cellSize.height += self.footerHeight; - } cellSize = CGSizeCeil(cellSize); @@ -532,15 +389,9 @@ NS_ASSUME_NONNULL_BEGIN self.dateHeaderLabel.text = nil; self.dateHeaderLabel.hidden = YES; - self.footerLabel.text = nil; - self.footerLabel.hidden = YES; self.avatarView.image = nil; self.avatarView.hidden = YES; - [self.expirationTimerView clearAnimations]; - [self.expirationTimerView removeFromSuperview]; - self.expirationTimerView = nil; - [self hideMenuControllerIfNecessary]; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -559,15 +410,7 @@ NS_ASSUME_NONNULL_BEGIN [self ensureMediaLoadState]; - if (isCellVisible) { - if (self.message.shouldStartExpireTimer) { - [self.expirationTimerView ensureAnimations]; - } else { - [self.expirationTimerView clearAnimations]; - } - } else { - [self.expirationTimerView clearAnimations]; - + if (!isCellVisible) { [self hideMenuControllerIfNecessary]; } } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m index a89e34c70..215eae8d1 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -4,7 +4,6 @@ #import "OWSMessageFooterView.h" #import "DateUtil.h" -#import "OWSExpirationTimerView.h" #import "Signal-Swift.h" NS_ASSUME_NONNULL_BEGIN @@ -39,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN self.timestampLabel = [UILabel new]; // TODO: Color self.timestampLabel.textColor = [UIColor lightGrayColor]; + [self addSubview:self.timestampLabel]; self.statusLabel = [UILabel new]; // TODO: Color @@ -56,6 +56,7 @@ NS_ASSUME_NONNULL_BEGIN ]]; statusStackView.axis = UILayoutConstraintAxisHorizontal; statusStackView.spacing = self.hSpacing; + [self addSubview:statusStackView]; [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]; [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationStyle.swift b/Signal/src/ViewControllers/ConversationView/ConversationStyle.swift index ea7cb6576..26aedecf3 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationStyle.swift +++ b/Signal/src/ViewControllers/ConversationView/ConversationStyle.swift @@ -4,34 +4,6 @@ import Foundation -@objc -public class OWSDirectionalEdgeInsets: NSObject { - - @objc public let leading: CGFloat - @objc public let trailing: CGFloat - @objc public let top: CGFloat - @objc public let bottom: CGFloat - - @objc - public required init(top: CGFloat = 0, - leading: CGFloat = 0, - bottom: CGFloat = 0, - trailing: CGFloat = 0) { - - self.leading = leading - self.trailing = trailing - self.top = top - self.bottom = bottom - - super.init() - } - - static var zero = OWSDirectionalEdgeInsets(top: 0, - leading: 0, - bottom: 0, - trailing: 0) -} - @objc public class ConversationStyle: NSObject { @@ -70,7 +42,9 @@ public class ConversationStyle: NSObject { // message status inside the message bubbles. @objc public var maxFooterWidth: CGFloat = 0 - @objc public var textInsets = OWSDirectionalEdgeInsets.zero + @objc public var textInsetTop: CGFloat = 0 + @objc public var textInsetBottom: CGFloat = 0 + @objc public var textInsetHorizontal: CGFloat = 0 // We want to align "group sender" avatars with the v-center of the // "last line" of the message body text - or where it would be for @@ -130,16 +104,13 @@ public class ConversationStyle: NSObject { let messageTextFont = UIFont.ows_dynamicTypeBody // Don't include the distance from the "cap height" to the top of the UILabel // in the top margin. - let textInsetTop = max(0, 12 - (messageTextFont.ascender - messageTextFont.capHeight)) + textInsetTop = max(0, 12 - (messageTextFont.ascender - messageTextFont.capHeight)) // Don't include the distance from the "baseline" to the bottom of the UILabel // (e.g. the descender) in the top margin. Note that UIFont.descender is a // negative value. - let textInsetBottom = max(0, 12 - abs(messageTextFont.descender)) + textInsetBottom = max(0, 12 - abs(messageTextFont.descender)) + textInsetHorizontal = 12 - textInsets = OWSDirectionalEdgeInsets(top: textInsetTop, - leading: 12, - bottom: textInsetBottom, - trailing: 12) lastTextLineAxis = CGFloat(round(12 + messageTextFont.capHeight * 0.5)) } } diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m index 0e1132786..6aacf932f 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -74,6 +74,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; for (OWSSignalServiceProtosAttachmentPointer *attachmentProto in attachmentProtos) { TSAttachmentPointer *pointer = [TSAttachmentPointer attachmentPointerFromProto:attachmentProto relay:relay]; + DDLogVerbose(@"%@ ---- creating attachment: %@", self.logTag, pointer.uniqueId); + [DDLog flushLog]; [attachmentIds addObject:pointer.uniqueId]; [pointer saveWithTransaction:transaction]; [attachmentPointers addObject:pointer]; diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 6bd4c892e..d06533ff1 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -561,6 +561,9 @@ NS_ASSUME_NONNULL_BEGIN return; } + DDLogVerbose(@"%@ ---- creating message: %@", self.logTag, attachmentsProcessor.attachmentIds); + [DDLog flushLog]; + TSIncomingMessage *_Nullable createdMessage = [self handleReceivedEnvelope:envelope withDataMessage:dataMessage attachmentIds:attachmentsProcessor.attachmentIds @@ -1107,6 +1110,9 @@ NS_ASSUME_NONNULL_BEGIN return; } + DDLogVerbose(@"%@ ---- creating message 2: %@", self.logTag, incomingMessage.attachmentIds); + [DDLog flushLog]; + [incomingMessage saveWithTransaction:transaction]; // Any messages sent from the current user - from this device or another - should be automatically marked as read. From 6626e2ecc39919382eac182687ad8174cbee50d6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Jun 2018 10:02:27 -0400 Subject: [PATCH 4/7] Introduce message cell footer view. --- .../Cells/OWSMessageBubbleView.m | 20 ++++----- .../Cells/OWSMessageFooterView.h | 2 + .../Cells/OWSMessageFooterView.m | 42 +++++++++++++++++-- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 089838cbe..c6f662907 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -383,15 +383,14 @@ NS_ASSUME_NONNULL_BEGIN } } - OWSMessageTextView *_Nullable bodyTextView = nil; - UIStackView *_Nullable textStackView = [UIStackView new]; + UIStackView *_Nullable textStackView = nil; // We render malformed messages as "empty text" messages, // so create a text view if there is no body media view. if (self.hasBodyText || !bodyMediaView) { + OWSMessageTextView *_Nullable bodyTextView = nil; bodyTextView = [self configureBodyTextView]; - } - if (bodyTextView) { + textStackView = [UIStackView new]; textStackView.axis = UILayoutConstraintAxisVertical; textStackView.alignment = UIStackViewAlignmentFill; @@ -406,8 +405,6 @@ NS_ASSUME_NONNULL_BEGIN [textStackView addArrangedSubview:bodyTextView]; [self.viewConstraints addObjectsFromArray:@[ - // [bodyTextView autoSetDimension:ALDimensionWidth toSize:bodyTextContentSize.width - // relation:NSLayoutRelationLessThanOrEqual], [bodyTextView autoSetDimension:ALDimensionHeight toSize:bodyTextContentSize.height], ]]; @@ -415,8 +412,6 @@ NS_ASSUME_NONNULL_BEGIN if (tapForMoreLabel) { [textStackView addArrangedSubview:tapForMoreLabel]; [self.viewConstraints addObjectsFromArray:@[ - // [tapForMoreLabel autoPinEdge:ALEdgeTop - // toEdge:ALEdgeBottom ofView:lastSubview], [tapForMoreLabel autoSetDimension:ALDimensionHeight toSize:self.tapForMoreHeight], ]]; } @@ -426,6 +421,7 @@ NS_ASSUME_NONNULL_BEGIN [footerView configureWithConversationViewItem:self.viewItem]; if (textStackView) { [textStackView addArrangedSubview:self.footerView]; + [self.footerView setHasShadows:NO viewItem:self.viewItem]; } else if (bodyMediaView) { [bodyMediaView addSubview:footerView]; @@ -435,7 +431,7 @@ NS_ASSUME_NONNULL_BEGIN [footerView autoPinTrailingToSuperviewMarginWithInset:self.conversationStyle.textInsetHorizontal], [footerView autoPinBottomToSuperviewMarginWithInset:self.conversationStyle.textInsetBottom], ]]; - // TODO: Drop shadow. + [self.footerView setHasShadows:YES viewItem:self.viewItem]; } else { // Display footer over media. OWSFail(@"%@ could not display footer.", self.logTag); @@ -1023,7 +1019,7 @@ NS_ASSUME_NONNULL_BEGIN // contact shares. if (self.hasFooter && self.hasBodyText) { CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem]; - cellSize.width = MAX(cellSize.width, footerSize.width); + cellSize.width = MAX(cellSize.width, footerSize.width + self.conversationStyle.textInsetHorizontal * 2); cellSize.height += self.textViewVSpacing + footerSize.height; } @@ -1110,6 +1106,10 @@ NS_ASSUME_NONNULL_BEGIN self.quotedMessageView = nil; [self.footerView removeFromSuperview]; + + for (UIView *subview in self.stackView.subviews) { + [subview removeFromSuperview]; + } } #pragma mark - Gestures diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h index e837d6ad8..a3d1dfcda 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN - (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem; +- (void)setHasShadows:(BOOL)hasShadows viewItem:(ConversationViewItem *)viewItem; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m index 215eae8d1..656b17a60 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -84,7 +84,7 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)statusIndicatorSize { // TODO: Review constant. - return 20.f; + return 16.f; } - (CGFloat)hSpacing @@ -100,10 +100,9 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(viewItem); [self configureLabelsWithConversationViewItem:viewItem]; - ; // TODO: - self.statusIndicatorView.backgroundColor = [UIColor ows_materialBlueColor]; + self.statusIndicatorView.backgroundColor = [UIColor redColor]; } - (void)configureLabelsWithConversationViewItem:(ConversationViewItem *)viewItem @@ -123,7 +122,6 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(viewItem); [self configureLabelsWithConversationViewItem:viewItem]; - ; CGSize result = CGSizeZero; result.height @@ -146,6 +144,42 @@ NS_ASSUME_NONNULL_BEGIN return statusMessage; } +#pragma mark - Shadows + +- (void)setHasShadows:(BOOL)hasShadows viewItem:(ConversationViewItem *)viewItem +{ + // TODO: Constants + for (UIView *subview in @[ + self.timestampLabel, + self.statusLabel, + self.statusIndicatorView, + ]) { + if (hasShadows) { + subview.layer.shadowColor = [UIColor blackColor].CGColor; + subview.layer.shadowOpacity = 0.35f; + subview.layer.shadowOffset = CGSizeZero; + subview.layer.shadowRadius = 0.5f; + } else { + subview.layer.shadowColor = nil; + subview.layer.shadowOpacity = 0.f; + subview.layer.shadowOffset = CGSizeZero; + subview.layer.shadowRadius = 0.f; + } + } + + UIColor *textColor; + if (hasShadows) { + textColor = [UIColor whiteColor]; + } else if (viewItem.interaction.interactionType == OWSInteractionType_IncomingMessage) { + // TODO: + textColor = [UIColor lightGrayColor]; + } else { + textColor = [UIColor whiteColor]; + } + self.timestampLabel.textColor = textColor; + self.statusLabel.textColor = textColor; +} + @end NS_ASSUME_NONNULL_END From 7d5ad0e165af67106cc9ec60caaf2a2591245fe6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Jun 2018 10:17:03 -0400 Subject: [PATCH 5/7] Introduce message cell footer view. --- .../Cells/OWSMessageFooterView.h | 2 +- .../Cells/OWSMessageFooterView.m | 91 ++++++++++++------- .../ViewControllers/HomeView/HomeViewCell.m | 13 +-- Signal/src/util/DateUtil.h | 3 + Signal/src/util/DateUtil.m | 23 +++++ 5 files changed, 86 insertions(+), 46 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h index a3d1dfcda..28836b316 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.h @@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface OWSMessageFooterView : UIView +@interface OWSMessageFooterView : UIStackView - (void)configureWithConversationViewItem:(ConversationViewItem *)viewItem; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m index 656b17a60..4d854e09e 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSMessageFooterView () @property (nonatomic) UILabel *timestampLabel; +@property (nonatomic) UIView *spacerView; @property (nonatomic) UILabel *statusLabel; @property (nonatomic) UIView *statusIndicatorView; @@ -35,10 +36,16 @@ NS_ASSUME_NONNULL_BEGIN self.layoutMargins = UIEdgeInsetsZero; + self.axis = UILayoutConstraintAxisHorizontal; + self.spacing = self.hSpacing; + self.alignment = UIStackViewAlignmentCenter; + self.timestampLabel = [UILabel new]; // TODO: Color self.timestampLabel.textColor = [UIColor lightGrayColor]; - [self addSubview:self.timestampLabel]; + + self.spacerView = [UIView new]; + [self.spacerView setContentHuggingLow]; self.statusLabel = [UILabel new]; // TODO: Color @@ -49,30 +56,30 @@ NS_ASSUME_NONNULL_BEGIN [self.statusIndicatorView autoSetDimension:ALDimensionHeight toSize:self.statusIndicatorSize]; self.statusIndicatorView.layer.cornerRadius = self.statusIndicatorSize * 0.5f; - // TODO: Review constant with Myles.0 - UIStackView *statusStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ - self.statusLabel, - self.statusIndicatorView, - ]]; - statusStackView.axis = UILayoutConstraintAxisHorizontal; - statusStackView.spacing = self.hSpacing; - [self addSubview:statusStackView]; - - [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]; - [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; - [self.timestampLabel autoVCenterInSuperview]; - [statusStackView autoVCenterInSuperview]; - [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; - [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom - withInset:0 - relation:NSLayoutRelationGreaterThanOrEqual]; - [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; - [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; - [statusStackView autoPinEdge:ALEdgeLeading - toEdge:ALEdgeTrailing - ofView:self.timestampLabel - withOffset:self.hSpacing - relation:NSLayoutRelationGreaterThanOrEqual]; + // // TODO: Review constant with Myles.0 + // UIStackView *statusStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ + // self.statusLabel, + // self.statusIndicatorView, + // ]]; + // statusStackView.axis = UILayoutConstraintAxisHorizontal; + // statusStackView.spacing = self.hSpacing; + // [self addSubview:statusStackView]; + + // [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]; + // [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; + // [self.timestampLabel autoVCenterInSuperview]; + // [statusStackView autoVCenterInSuperview]; + // [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 + // relation:NSLayoutRelationGreaterThanOrEqual]; [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom + // withInset:0 + // relation:NSLayoutRelationGreaterThanOrEqual]; + // [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 + // relation:NSLayoutRelationGreaterThanOrEqual]; [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeBottom + // withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [statusStackView autoPinEdge:ALEdgeLeading + // toEdge:ALEdgeTrailing + // ofView:self.timestampLabel + // withOffset:self.hSpacing + // relation:NSLayoutRelationGreaterThanOrEqual]; } - (void)configureFonts @@ -84,13 +91,13 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)statusIndicatorSize { // TODO: Review constant. - return 16.f; + return 12.f; } - (CGFloat)hSpacing { // TODO: Review constant. - return 10.f; + return 8.f; } #pragma mark - Load @@ -102,7 +109,23 @@ NS_ASSUME_NONNULL_BEGIN [self configureLabelsWithConversationViewItem:viewItem]; // TODO: - self.statusIndicatorView.backgroundColor = [UIColor redColor]; + self.statusIndicatorView.backgroundColor = [UIColor orangeColor]; + + for (UIView *subview in @[ + self.timestampLabel, + self.statusLabel, + self.statusIndicatorView, + ]) { + [subview removeFromSuperview]; + } + if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { + [self addArrangedSubview:self.timestampLabel]; + [self addArrangedSubview:self.spacerView]; + [self addArrangedSubview:self.statusLabel]; + [self addArrangedSubview:self.statusIndicatorView]; + } else { + [self addArrangedSubview:self.timestampLabel]; + } } - (void)configureLabelsWithConversationViewItem:(ConversationViewItem *)viewItem @@ -111,9 +134,7 @@ NS_ASSUME_NONNULL_BEGIN [self configureFonts]; - // TODO: Correct text. - self.timestampLabel.text = - [DateUtil formatPastTimestampRelativeToNow:viewItem.interaction.timestamp isRTL:CurrentAppContext().isRTL]; + self.timestampLabel.text = [DateUtil formatTimestampShort:viewItem.interaction.timestamp]; self.statusLabel.text = [self messageStatusTextForConversationViewItem:viewItem]; } @@ -126,8 +147,12 @@ NS_ASSUME_NONNULL_BEGIN CGSize result = CGSizeZero; result.height = MAX(self.timestampLabel.font.lineHeight, MAX(self.statusLabel.font.lineHeight, self.statusIndicatorSize)); - result.width = ([self.timestampLabel sizeThatFits:CGSizeZero].width + - [self.statusLabel sizeThatFits:CGSizeZero].width + self.statusIndicatorSize + self.hSpacing * 2.f); + if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { + result.width = ([self.timestampLabel sizeThatFits:CGSizeZero].width + + [self.statusLabel sizeThatFits:CGSizeZero].width + self.statusIndicatorSize + self.hSpacing * 3.f); + } else { + result.width = [self.timestampLabel sizeThatFits:CGSizeZero].width; + } return CGSizeCeil(result); } diff --git a/Signal/src/ViewControllers/HomeView/HomeViewCell.m b/Signal/src/ViewControllers/HomeView/HomeViewCell.m index abc1af63c..fa9feb95a 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewCell.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewCell.m @@ -342,18 +342,7 @@ NS_ASSUME_NONNULL_BEGIN return @""; } - NSString *dateTimeString; - if (![DateUtil dateIsThisYear:date]) { - dateTimeString = [[DateUtil dateFormatter] stringFromDate:date]; - } else if ([DateUtil dateIsOlderThanOneWeek:date]) { - dateTimeString = [[DateUtil monthAndDayFormatter] stringFromDate:date]; - } else if ([DateUtil dateIsOlderThanToday:date]) { - dateTimeString = [[DateUtil shortDayOfWeekFormatter] stringFromDate:date]; - } else { - dateTimeString = [[DateUtil timeFormatter] stringFromDate:date]; - } - - return dateTimeString.uppercaseString; + return [DateUtil formatDateShort:date]; } #pragma mark - Constants diff --git a/Signal/src/util/DateUtil.h b/Signal/src/util/DateUtil.h index 0ef6bf2d1..913808b2e 100644 --- a/Signal/src/util/DateUtil.h +++ b/Signal/src/util/DateUtil.h @@ -20,6 +20,9 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp isRTL:(BOOL)isRTL NS_SWIFT_NAME(formatPastTimestampRelativeToNow(_:isRTL:)); ++ (NSString *)formatTimestampShort:(uint64_t)timestamp; ++ (NSString *)formatDateShort:(NSDate *)date; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/DateUtil.m b/Signal/src/util/DateUtil.m index 168728be2..3152b6e92 100644 --- a/Signal/src/util/DateUtil.m +++ b/Signal/src/util/DateUtil.m @@ -165,6 +165,29 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE"; isRTL:isRTL]; } ++ (NSString *)formatTimestampShort:(uint64_t)timestamp +{ + return [self formatDateShort:[NSDate ows_dateWithMillisecondsSince1970:timestamp]]; +} + ++ (NSString *)formatDateShort:(NSDate *)date +{ + OWSAssert(date); + + NSString *dateTimeString; + if (![DateUtil dateIsThisYear:date]) { + dateTimeString = [[DateUtil dateFormatter] stringFromDate:date]; + } else if ([DateUtil dateIsOlderThanOneWeek:date]) { + dateTimeString = [[DateUtil monthAndDayFormatter] stringFromDate:date]; + } else if ([DateUtil dateIsOlderThanToday:date]) { + dateTimeString = [[DateUtil shortDayOfWeekFormatter] stringFromDate:date]; + } else { + dateTimeString = [[DateUtil timeFormatter] stringFromDate:date]; + } + + return dateTimeString.uppercaseString; +} + @end NS_ASSUME_NONNULL_END From 18417edbde9049621393775d71b19a878aa279c6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Jun 2018 10:31:05 -0400 Subject: [PATCH 6/7] Introduce message cell footer view. --- .../Cells/OWSMessageBubbleView.m | 28 ------------------- .../Cells/OWSMessageFooterView.m | 25 ----------------- .../Attachments/OWSAttachmentsProcessor.m | 2 -- .../src/Messages/OWSMessageManager.m | 6 ---- .../src/Storage/FullTextSearchFinder.swift | 3 +- 5 files changed, 1 insertion(+), 63 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index c6f662907..9eba65a80 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -269,8 +269,6 @@ NS_ASSUME_NONNULL_BEGIN self.bubbleView.bubbleColor = nil; } - // CGFloat bottomMargin = 0; - if (self.isQuotedReply) { BOOL isOutgoing = [self.viewItem.interaction isKindOfClass:TSOutgoingMessage.class]; DisplayableText *_Nullable displayableQuotedText @@ -288,8 +286,6 @@ NS_ASSUME_NONNULL_BEGIN [self.viewConstraints addObject:[quotedMessageView autoSetDimension:ALDimensionHeight toSize:quotedMessageContentSize.height]]; - // bottomMargin = 0; - [self.bubbleView addPartnerView:quotedMessageView.boundsStrokeView]; } @@ -344,27 +340,6 @@ NS_ASSUME_NONNULL_BEGIN } [self.stackView addArrangedSubview:bodyMediaView]; - // // This layout can lead to extreme cropping of media content, - // // e.g. a very tall portrait image + long caption. The media - // // view will have "max width", so the image will be cropped to - // // roughly a square. - // // TODO: Myles is considering alternatives. - // [self.viewConstraints addObjectsFromArray:@[ - // [bodyMediaView autoPinLeadingToSuperviewMarginWithInset:0], - // [bodyMediaView autoPinTrailingToSuperviewMarginWithInset:0], - // ]]; - // // We need constraints to control the vertical sizing of the media view, but we use - // // lower priority so that when a message only contains media it uses the exact bounds of - // // the message view. - // [NSLayoutConstraint - // autoSetPriority:UILayoutPriorityDefaultLow - // forConstraints:^{ - // [self.viewConstraints - // addObject:[bodyMediaView autoSetDimension:ALDimensionHeight - // toSize:bodyMediaContentSize.height]]; - // }]; - - // bottomMargin = 0; BOOL shouldStrokeMediaView = ([bodyMediaView isKindOfClass:[UIImageView class]] || [bodyMediaView isKindOfClass:[OWSContactShareView class]]); @@ -437,13 +412,10 @@ NS_ASSUME_NONNULL_BEGIN OWSFail(@"%@ could not display footer.", self.logTag); } - // TODO: Should we do this for media content too? if (textStackView) { CGSize bubbleSize = [self measureSize]; [self.viewConstraints addObjectsFromArray:@[ [self autoSetDimension:ALDimensionWidth toSize:bubbleSize.width relation:NSLayoutRelationLessThanOrEqual], - // [bodyTextView autoSetDimension:ALDimensionHeight - // toSize:bodyTextContentSize.height], ]]; } } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m index 4d854e09e..33680ccc1 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -55,31 +55,6 @@ NS_ASSUME_NONNULL_BEGIN [self.statusIndicatorView autoSetDimension:ALDimensionWidth toSize:self.statusIndicatorSize]; [self.statusIndicatorView autoSetDimension:ALDimensionHeight toSize:self.statusIndicatorSize]; self.statusIndicatorView.layer.cornerRadius = self.statusIndicatorSize * 0.5f; - - // // TODO: Review constant with Myles.0 - // UIStackView *statusStackView = [[UIStackView alloc] initWithArrangedSubviews:@[ - // self.statusLabel, - // self.statusIndicatorView, - // ]]; - // statusStackView.axis = UILayoutConstraintAxisHorizontal; - // statusStackView.spacing = self.hSpacing; - // [self addSubview:statusStackView]; - - // [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading]; - // [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing]; - // [self.timestampLabel autoVCenterInSuperview]; - // [statusStackView autoVCenterInSuperview]; - // [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 - // relation:NSLayoutRelationGreaterThanOrEqual]; [self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom - // withInset:0 - // relation:NSLayoutRelationGreaterThanOrEqual]; - // [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:0 - // relation:NSLayoutRelationGreaterThanOrEqual]; [statusStackView autoPinEdgeToSuperviewEdge:ALEdgeBottom - // withInset:0 relation:NSLayoutRelationGreaterThanOrEqual]; [statusStackView autoPinEdge:ALEdgeLeading - // toEdge:ALEdgeTrailing - // ofView:self.timestampLabel - // withOffset:self.hSpacing - // relation:NSLayoutRelationGreaterThanOrEqual]; } - (void)configureFonts diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m index 6aacf932f..0e1132786 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -74,8 +74,6 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; for (OWSSignalServiceProtosAttachmentPointer *attachmentProto in attachmentProtos) { TSAttachmentPointer *pointer = [TSAttachmentPointer attachmentPointerFromProto:attachmentProto relay:relay]; - DDLogVerbose(@"%@ ---- creating attachment: %@", self.logTag, pointer.uniqueId); - [DDLog flushLog]; [attachmentIds addObject:pointer.uniqueId]; [pointer saveWithTransaction:transaction]; [attachmentPointers addObject:pointer]; diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index d06533ff1..6bd4c892e 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -561,9 +561,6 @@ NS_ASSUME_NONNULL_BEGIN return; } - DDLogVerbose(@"%@ ---- creating message: %@", self.logTag, attachmentsProcessor.attachmentIds); - [DDLog flushLog]; - TSIncomingMessage *_Nullable createdMessage = [self handleReceivedEnvelope:envelope withDataMessage:dataMessage attachmentIds:attachmentsProcessor.attachmentIds @@ -1110,9 +1107,6 @@ NS_ASSUME_NONNULL_BEGIN return; } - DDLogVerbose(@"%@ ---- creating message 2: %@", self.logTag, incomingMessage.attachmentIds); - [DDLog flushLog]; - [incomingMessage saveWithTransaction:transaction]; // Any messages sent from the current user - from this device or another - should be automatically marked as read. diff --git a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift index 39e935d30..13557e32f 100644 --- a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift +++ b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift @@ -211,8 +211,7 @@ public class FullTextSearchFinder: NSObject { var oversizeText: String? dbConnection.read({ (transaction) in guard let attachment = message.attachment(with: transaction) else { - // This can happen during the initial save of incoming messages. - Logger.warn("Could not load attachment for search indexing.") + owsFail("Could not load attachment for search indexing.") return } guard let attachmentStream = attachment as? TSAttachmentStream else { From 3fba101421d2ef15432b583b10019ce24e6d6a9e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 26 Jun 2018 13:32:05 -0400 Subject: [PATCH 7/7] Respond to CR. --- .../ic_hourglass_empty.imageset/Contents.json | 26 ------------------ .../ic_hourglass_empty.png | Bin 271 -> 0 bytes .../ic_hourglass_empty@2x.png | Bin 315 -> 0 bytes .../ic_hourglass_empty@3x.png | Bin 421 -> 0 bytes .../ic_hourglass_full.imageset/Contents.json | 26 ------------------ .../ic_hourglass_full.png | Bin 207 -> 0 bytes .../ic_hourglass_full@2x.png | Bin 231 -> 0 bytes .../ic_hourglass_full@3x.png | Bin 367 -> 0 bytes .../Cells/OWSMessageBubbleView.m | 3 +- .../Cells/OWSMessageFooterView.m | 17 +++++------- 10 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 Signal/Images.xcassets/ic_hourglass_empty.imageset/Contents.json delete mode 100644 Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty.png delete mode 100644 Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty@2x.png delete mode 100644 Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty@3x.png delete mode 100644 Signal/Images.xcassets/ic_hourglass_full.imageset/Contents.json delete mode 100644 Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full.png delete mode 100644 Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full@2x.png delete mode 100644 Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full@3x.png diff --git a/Signal/Images.xcassets/ic_hourglass_empty.imageset/Contents.json b/Signal/Images.xcassets/ic_hourglass_empty.imageset/Contents.json deleted file mode 100644 index c660cb9a9..000000000 --- a/Signal/Images.xcassets/ic_hourglass_empty.imageset/Contents.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_hourglass_empty.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ic_hourglass_empty@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ic_hourglass_empty@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty.png b/Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty.png deleted file mode 100644 index ea3f048555f388f4687da5be4b4c59c74928c406..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XHhQ`^hE&{2N?0J6a769L|Nr%@3QHZ9Nb*ZaO3Di?70M_$ zT>r?C*~~bCV<%hJM#eq+lu|x8EijiZ_{rsQ_3@<>E~`&0-hEJhL5bak(`*qh=4HyzCnnCMg)q*LlA{+E3KEP{MD^LX&QrU+k>DO8hGpGR$6R!K1IB z+QYiwi%Y_^eGP#sPDj)ht(1KfIV<3$7(b75warnrn{1PVCi5g*b>U?YNMMlj`?<*E SOr<%{@eH1>elF{r5}E)-PGM01 diff --git a/Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty@2x.png b/Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty@2x.png deleted file mode 100644 index 49bc8a4a60032a160a7dd06474954eb61824cb6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 315 zcmV-B0mS}^P)@Zi3aKn_{JC7ucg{SJ*wYOY9E12sYeFcdn8M)pVh@wNM8~sG~XD$Bza34_ynq z8s=HGV9UYrYLSbdP=KR@Eq80laKW9li{e5XHEgWmdxyY2>?_^^3;=Rmv;N-tpHlz; N002ovPDHLkV1k7|fyV#< diff --git a/Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty@3x.png b/Signal/Images.xcassets/ic_hourglass_empty.imageset/ic_hourglass_empty@3x.png deleted file mode 100644 index 8a693939d9e34962dc92dab6135f19dedeb87f15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|1|%PcFuVX#k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7~MTx978H@y}9+Cx5+@DJuzFRL$HL|Jwf;;k6VH8-38NH z`jryid%dry@BG;OwWwC3epZ2%pc6-=&k0?DxHWH{H(86^;TYa zV0-SlYNO$`8Chqa#&Nr?Zr#YVaBXgCNAe8k3kM9k6*?2;9GMRDI`Oqxx6~WlQOY@3 z(7%E4u>B#gp1Xox@9gHk{Cv;gpY7k{SF?VU@>b~nxhBK9Fw*##?IHW*yGlEbo|rj7 zny0gK#_%wGIWZ`(EN%WFWMIP~gh(TW5)=JrVfPFzov4=}{mKC3yrxd9aJ N44$rjF6*2UngEQ7t%3jm diff --git a/Signal/Images.xcassets/ic_hourglass_full.imageset/Contents.json b/Signal/Images.xcassets/ic_hourglass_full.imageset/Contents.json deleted file mode 100644 index 9f72c5b3f..000000000 --- a/Signal/Images.xcassets/ic_hourglass_full.imageset/Contents.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_hourglass_full.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ic_hourglass_full@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ic_hourglass_full@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full.png b/Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full.png deleted file mode 100644 index f5158ed760efa5aa2d281673ea8e7b7c166d1568..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X3O!vMLn>}1B`gq3IHLCB|NnYcg{2NlB>5#ICFKQ{3S|@= zu7BjnY-T(oLsVojo3P0bX3n%4g_CXxIp@`tT+gVVwr1g(?;jZP(4gjDgR9)%<|eg? z>{kLDoFW4qyd=0MJm71*xG5=tXIcJL7hVQ|1csGqye`XL>k0xbXYh3Ob6Mw<&;$V5 ClS0w} diff --git a/Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full@2x.png b/Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full@2x.png deleted file mode 100644 index 933dd350a5df0a12f2b8d5fb84bafa6c18140f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8NdcWQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8PEQxdkcwMxt~v5GDDW^Gy!IpD59hu;$CTYSuu7GmUoumG z)um(Bmh_D?uL`te^)f{ZL@+Y5@kkgX9LRTn{zm(NwBhEPPm}7VYWmx#f00=uT&4F- zxr(<;V6WKi&a#7YpSB0p3azZ$6>>akRk!lht8aQ_oj%J!3~1QSG=J{qe8pR<-5VGg Yc&oivY&({w0(2CEr>mdKI;Vst0Hgj>TL1t6 diff --git a/Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full@3x.png b/Signal/Images.xcassets/ic_hourglass_full.imageset/ic_hourglass_full@3x.png deleted file mode 100644 index b0c4631c0d6262c9646e1aaa30c2078b45f9c5f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|1|%PcFuVX#k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7zI6D978H@y}5m#w^>2pcwweh!(xjjspgksr)a}tIER>?=4 zUbF}bJ4z~>H3=Ov6_7GKBcStM>bYUX|3`oCp1l}ZXr{AuPu&c`=jNyPE61=qGb-A( zDLKXT@Ns-Juu?j5(7@qCe`3c2@nnW3`<_OQRT5I}Z`^f1AJ{nKelADx^#WNGM}ge< qLcVwZpRVRQk+w+!PCP{%1`MkIGHo*Ed=mkNAcLo?pUXO@geCwb35D4J diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 9eba65a80..a72b3bce6 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -395,9 +395,11 @@ NS_ASSUME_NONNULL_BEGIN OWSMessageFooterView *footerView = self.footerView; [footerView configureWithConversationViewItem:self.viewItem]; if (textStackView) { + // Display footer below text. [textStackView addArrangedSubview:self.footerView]; [self.footerView setHasShadows:NO viewItem:self.viewItem]; } else if (bodyMediaView) { + // Display footer over media. [bodyMediaView addSubview:footerView]; bodyMediaView.layoutMargins = UIEdgeInsetsZero; @@ -408,7 +410,6 @@ NS_ASSUME_NONNULL_BEGIN ]]; [self.footerView setHasShadows:YES viewItem:self.viewItem]; } else { - // Display footer over media. OWSFail(@"%@ could not display footer.", self.logTag); } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m index 33680ccc1..dec5be284 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -43,18 +43,22 @@ NS_ASSUME_NONNULL_BEGIN self.timestampLabel = [UILabel new]; // TODO: Color self.timestampLabel.textColor = [UIColor lightGrayColor]; + [self addArrangedSubview:self.timestampLabel]; self.spacerView = [UIView new]; [self.spacerView setContentHuggingLow]; + [self addArrangedSubview:self.spacerView]; self.statusLabel = [UILabel new]; // TODO: Color self.statusLabel.textColor = [UIColor lightGrayColor]; + [self addArrangedSubview:self.statusLabel]; self.statusIndicatorView = [UIView new]; [self.statusIndicatorView autoSetDimension:ALDimensionWidth toSize:self.statusIndicatorSize]; [self.statusIndicatorView autoSetDimension:ALDimensionHeight toSize:self.statusIndicatorSize]; self.statusIndicatorView.layer.cornerRadius = self.statusIndicatorSize * 0.5f; + [self addArrangedSubview:self.statusIndicatorView]; } - (void)configureFonts @@ -86,20 +90,13 @@ NS_ASSUME_NONNULL_BEGIN // TODO: self.statusIndicatorView.backgroundColor = [UIColor orangeColor]; + BOOL isOutgoing = (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage); for (UIView *subview in @[ - self.timestampLabel, + self.spacerView, self.statusLabel, self.statusIndicatorView, ]) { - [subview removeFromSuperview]; - } - if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { - [self addArrangedSubview:self.timestampLabel]; - [self addArrangedSubview:self.spacerView]; - [self addArrangedSubview:self.statusLabel]; - [self addArrangedSubview:self.statusIndicatorView]; - } else { - [self addArrangedSubview:self.timestampLabel]; + subview.hidden = !isOutgoing; } }