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 */; };
|
34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0721F8678AA0066283D /* ConversationViewLayout.m */; };
|
||||||
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0971F867BFC0066283D /* ConversationViewCell.m */; };
|
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0971F867BFC0066283D /* ConversationViewCell.m */; };
|
||||||
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F09B1F867BFC0066283D /* OWSContactOffersCell.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 */; };
|
34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A21F867BFC0066283D /* OWSMessageCell.m */; };
|
||||||
34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */; };
|
34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */; };
|
||||||
34D1F0B11F867BFC0066283D /* OWSUnreadIndicatorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0A81F867BFC0066283D /* OWSUnreadIndicatorCell.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 */; };
|
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
|
||||||
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; };
|
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; };
|
||||||
34D920E220DD39EA00D51158 /* ConversationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E120DD39E900D51158 /* ConversationStyle.swift */; };
|
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 */; };
|
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; };
|
||||||
34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */; };
|
34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */; };
|
||||||
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBEFFF206BD5A400025978 /* OWSMessageTextView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1751,15 +1750,14 @@
|
||||||
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */,
|
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */,
|
||||||
34CA63192097806E00E526A0 /* OWSContactShareView.h */,
|
34CA63192097806E00E526A0 /* OWSContactShareView.h */,
|
||||||
34CA631A2097806E00E526A0 /* OWSContactShareView.m */,
|
34CA631A2097806E00E526A0 /* OWSContactShareView.m */,
|
||||||
34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */,
|
|
||||||
34D1F09D1F867BFC0066283D /* OWSExpirationTimerView.h */,
|
|
||||||
34D1F09E1F867BFC0066283D /* OWSExpirationTimerView.m */,
|
|
||||||
34D1F0B51F87F8850066283D /* OWSGenericAttachmentView.h */,
|
34D1F0B51F87F8850066283D /* OWSGenericAttachmentView.h */,
|
||||||
34D1F0B61F87F8850066283D /* OWSGenericAttachmentView.m */,
|
34D1F0B61F87F8850066283D /* OWSGenericAttachmentView.m */,
|
||||||
3496744B2076768600080B5F /* OWSMessageBubbleView.h */,
|
3496744B2076768600080B5F /* OWSMessageBubbleView.h */,
|
||||||
3496744C2076768700080B5F /* OWSMessageBubbleView.m */,
|
3496744C2076768700080B5F /* OWSMessageBubbleView.m */,
|
||||||
34D1F0A11F867BFC0066283D /* OWSMessageCell.h */,
|
34D1F0A11F867BFC0066283D /* OWSMessageCell.h */,
|
||||||
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */,
|
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */,
|
||||||
|
34D920E520E179C100D51158 /* OWSMessageFooterView.h */,
|
||||||
|
34D920E620E179C200D51158 /* OWSMessageFooterView.m */,
|
||||||
34DBF000206BD5A400025978 /* OWSMessageTextView.h */,
|
34DBF000206BD5A400025978 /* OWSMessageTextView.h */,
|
||||||
34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */,
|
34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */,
|
||||||
34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */,
|
34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */,
|
||||||
|
@ -3232,7 +3230,6 @@
|
||||||
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */,
|
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */,
|
||||||
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
|
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
|
||||||
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */,
|
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */,
|
||||||
34D1F0AC1F867BFC0066283D /* OWSExpirationTimerView.m in Sources */,
|
|
||||||
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
|
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
|
||||||
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */,
|
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */,
|
||||||
45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */,
|
45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */,
|
||||||
|
@ -3271,6 +3268,7 @@
|
||||||
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
||||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
|
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
|
||||||
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
|
34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */,
|
||||||
|
34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */,
|
||||||
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
|
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
|
||||||
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
|
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
|
||||||
457C87B82032645C008D52D6 /* DebugUINotifications.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 "OWSBubbleView.h"
|
||||||
#import "OWSContactShareView.h"
|
#import "OWSContactShareView.h"
|
||||||
#import "OWSGenericAttachmentView.h"
|
#import "OWSGenericAttachmentView.h"
|
||||||
|
#import "OWSMessageFooterView.h"
|
||||||
#import "OWSMessageTextView.h"
|
#import "OWSMessageTextView.h"
|
||||||
#import "OWSQuotedMessageView.h"
|
#import "OWSQuotedMessageView.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
|
@ -36,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
||||||
|
|
||||||
|
@property (nonatomic) OWSMessageFooterView *footerView;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation OWSMessageBubbleView
|
@implementation OWSMessageBubbleView
|
||||||
|
@ -73,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
self.bodyTextView.dataDetectorTypes
|
self.bodyTextView.dataDetectorTypes
|
||||||
= (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent);
|
= (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent);
|
||||||
self.bodyTextView.hidden = YES;
|
self.bodyTextView.hidden = YES;
|
||||||
|
|
||||||
|
self.footerView = [OWSMessageFooterView new];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (OWSMessageTextView *)newTextView
|
- (OWSMessageTextView *)newTextView
|
||||||
|
@ -436,6 +441,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
bottomMargin = textInsets.bottom;
|
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);
|
OWSAssert(lastSubview);
|
||||||
[self.viewConstraints addObjectsFromArray:@[
|
[self.viewConstraints addObjectsFromArray:@[
|
||||||
[lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:bottomMargin],
|
[lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:bottomMargin],
|
||||||
|
@ -1007,11 +1026,23 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
cellSize.height += self.tapForMoreHeight;
|
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);
|
cellSize = CGSizeCeil(cellSize);
|
||||||
|
|
||||||
return cellSize;
|
return cellSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)hasFooter
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
- (UIFont *)tapForMoreFont
|
- (UIFont *)tapForMoreFont
|
||||||
{
|
{
|
||||||
return UIFont.ows_dynamicTypeCaption1Font;
|
return UIFont.ows_dynamicTypeCaption1Font;
|
||||||
|
@ -1022,6 +1053,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25);
|
return (CGFloat)ceil([self tapForMoreFont].lineHeight * 1.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (CGFloat)footerVSpacing
|
||||||
|
{
|
||||||
|
return 10.f;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
- (UIColor *)bodyTextColor
|
- (UIColor *)bodyTextColor
|
||||||
|
@ -1082,6 +1118,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
[self.quotedMessageView removeFromSuperview];
|
[self.quotedMessageView removeFromSuperview];
|
||||||
self.quotedMessageView = nil;
|
self.quotedMessageView = nil;
|
||||||
|
|
||||||
|
[self.footerView removeFromSuperview];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Gestures
|
#pragma mark - Gestures
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
#import "OWSMessageCell.h"
|
#import "OWSMessageCell.h"
|
||||||
#import "OWSContactAvatarBuilder.h"
|
#import "OWSContactAvatarBuilder.h"
|
||||||
#import "OWSExpirationTimerView.h"
|
|
||||||
#import "OWSMessageBubbleView.h"
|
#import "OWSMessageBubbleView.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
|
|
||||||
|
@ -24,10 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@property (nonatomic) OWSMessageBubbleView *messageBubbleView;
|
@property (nonatomic) OWSMessageBubbleView *messageBubbleView;
|
||||||
@property (nonatomic) UILabel *dateHeaderLabel;
|
@property (nonatomic) UILabel *dateHeaderLabel;
|
||||||
@property (nonatomic) UIView *footerView;
|
|
||||||
@property (nonatomic) AvatarImageView *avatarView;
|
@property (nonatomic) AvatarImageView *avatarView;
|
||||||
@property (nonatomic) UILabel *footerLabel;
|
@property (nonatomic) OWSMessageFooterView *footerView2;
|
||||||
@property (nonatomic, nullable) OWSExpirationTimerView *expirationTimerView;
|
|
||||||
|
|
||||||
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
||||||
@property (nonatomic) BOOL isPresentingMenuController;
|
@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, readonly) BOOL hasQuotedText;
|
||||||
|
|
||||||
@property (nonatomic) BOOL shouldShowDate;
|
@property (nonatomic) BOOL shouldShowDate;
|
||||||
|
// TODO: Consider renaming to shouldHideFooter.
|
||||||
@property (nonatomic) BOOL shouldHideRecipientStatus;
|
@property (nonatomic) BOOL shouldHideRecipientStatus;
|
||||||
// Used to suppress "group sender" avatars.
|
// Used to suppress "group sender" avatars.
|
||||||
@property (nonatomic) BOOL shouldHideAvatar;
|
@property (nonatomic) BOOL shouldHideAvatar;
|
||||||
|
|
Loading…
Reference in New Issue