mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Introduce message cell footer view.
This commit is contained in:
parent
538cd4f893
commit
cbacda87ca
|
@ -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 = "<group>"; };
|
||||
34D1F09A1F867BFC0066283D /* OWSContactOffersCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactOffersCell.h; sourceTree = "<group>"; };
|
||||
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactOffersCell.m; sourceTree = "<group>"; };
|
||||
34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirableMessageView.h; sourceTree = "<group>"; };
|
||||
34D1F09D1F867BFC0066283D /* OWSExpirationTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSExpirationTimerView.h; sourceTree = "<group>"; };
|
||||
34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSExpirationTimerView.m; sourceTree = "<group>"; };
|
||||
34D1F0A11F867BFC0066283D /* OWSMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCell.h; sourceTree = "<group>"; };
|
||||
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageCell.m; sourceTree = "<group>"; };
|
||||
34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSSystemMessageCell.h; sourceTree = "<group>"; };
|
||||
|
@ -883,6 +880,8 @@
|
|||
34D8C02A1ED3685800188D7C /* DebugUIContacts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIContacts.m; sourceTree = "<group>"; };
|
||||
34D913491F62D4A500722898 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = "<group>"; };
|
||||
34D920E120DD39E900D51158 /* ConversationStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationStyle.swift; sourceTree = "<group>"; };
|
||||
34D920E520E179C100D51158 /* OWSMessageFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageFooterView.h; sourceTree = "<group>"; };
|
||||
34D920E620E179C200D51158 /* OWSMessageFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageFooterView.m; sourceTree = "<group>"; };
|
||||
34D99C8A1F27B13B00D284D6 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSViewController.h; sourceTree = "<group>"; };
|
||||
34D99C8B1F27B13B00D284D6 /* OWSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSViewController.m; sourceTree = "<group>"; };
|
||||
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = "<group>"; };
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
// TODO:
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
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
|
|
@ -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 <QuartzCore/QuartzCore.h>
|
||||
#import <SignalServiceKit/NSTimer+OWS.h>
|
||||
|
||||
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
|
|
@ -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<NSLayoutConstraint *> *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
|
||||
|
|
|
@ -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<NSLayoutConstraint *> *viewConstraints;
|
||||
@property (nonatomic) BOOL isPresentingMenuController;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue