Add views to render messages with per-message expiration in conversation view.
This commit is contained in:
parent
a6280e61c5
commit
08d1592cb1
@ -162,6 +162,7 @@
|
||||
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
|
||||
346E35BE224283B100E55D5F /* UIAlertController+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */; };
|
||||
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E9D5321B040B600562252 /* RegistrationController.swift */; };
|
||||
347137FC22A1BCE800F43A63 /* OWSMessageHiddenView.m in Sources */ = {isa = PBXBuildFile; fileRef = 347137FB22A1BCE800F43A63 /* OWSMessageHiddenView.m */; };
|
||||
347850311FD7494A007B8332 /* dripicons-v2.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5B1E787A9800DF2FB9 /* dripicons-v2.ttf */; };
|
||||
347850321FD7494A007B8332 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A5D1E787BD800DF2FB9 /* ElegantIcons.ttf */; };
|
||||
347850331FD7494A007B8332 /* fontawesome-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34330A591E7875FB00DF2FB9 /* fontawesome-webfont.ttf */; };
|
||||
@ -876,6 +877,8 @@
|
||||
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
|
||||
346E35BD224283B000E55D5F /* UIAlertController+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+OWS.swift"; sourceTree = "<group>"; };
|
||||
346E9D5321B040B600562252 /* RegistrationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = "<group>"; };
|
||||
347137FA22A1BCE800F43A63 /* OWSMessageHiddenView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageHiddenView.h; sourceTree = "<group>"; };
|
||||
347137FB22A1BCE800F43A63 /* OWSMessageHiddenView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageHiddenView.m; sourceTree = "<group>"; };
|
||||
347850561FD86544007B8332 /* SAEFailedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEFailedViewController.swift; sourceTree = "<group>"; };
|
||||
3478505A1FD999D5007B8332 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = translations/et.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3478505C1FD99A1F007B8332 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_TW; path = translations/zh_TW.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@ -2139,6 +2142,8 @@
|
||||
34D920E620E179C200D51158 /* OWSMessageFooterView.m */,
|
||||
348570A720F67574004FF32B /* OWSMessageHeaderView.h */,
|
||||
348570A620F67574004FF32B /* OWSMessageHeaderView.m */,
|
||||
347137FA22A1BCE800F43A63 /* OWSMessageHiddenView.h */,
|
||||
347137FB22A1BCE800F43A63 /* OWSMessageHiddenView.m */,
|
||||
34C2EEB82272244500BCA1D0 /* OWSMessageStickerView.h */,
|
||||
34C2EEB72272244500BCA1D0 /* OWSMessageStickerView.m */,
|
||||
34DBF000206BD5A400025978 /* OWSMessageTextView.h */,
|
||||
@ -3772,6 +3777,7 @@
|
||||
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
|
||||
3448E16422135FFA004B052E /* OnboardingPhoneNumberViewController.swift in Sources */,
|
||||
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
|
||||
347137FC22A1BCE800F43A63 /* OWSMessageHiddenView.m in Sources */,
|
||||
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
|
||||
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,
|
||||
4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */,
|
||||
|
||||
23
Signal/Images.xcassets/play-outline-24.imageset/Contents.json
vendored
Normal file
23
Signal/Images.xcassets/play-outline-24.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "play-outline-24@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "play-outline-24@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "play-outline-24@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Signal/Images.xcassets/play-outline-24.imageset/play-outline-24@1x.png
vendored
Normal file
BIN
Signal/Images.xcassets/play-outline-24.imageset/play-outline-24@1x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 318 B |
BIN
Signal/Images.xcassets/play-outline-24.imageset/play-outline-24@2x.png
vendored
Normal file
BIN
Signal/Images.xcassets/play-outline-24.imageset/play-outline-24@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
BIN
Signal/Images.xcassets/play-outline-24.imageset/play-outline-24@3x.png
vendored
Normal file
BIN
Signal/Images.xcassets/play-outline-24.imageset/play-outline-24@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@ -339,6 +339,7 @@ private class MockConversationViewItem: NSObject, ConversationViewItem {
|
||||
var stickerInfo: StickerInfo?
|
||||
var stickerAttachment: TSAttachmentStream?
|
||||
var isFailedSticker: Bool = false
|
||||
var perMessageExpirationHasExpired: Bool = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@ -38,6 +38,7 @@ typedef NS_OPTIONS(NSUInteger, OWSDirectionalRectCorner) {
|
||||
sharpCorners:(OWSDirectionalRectCorner)sharpCorners;
|
||||
|
||||
@property (nonatomic, nullable) UIColor *bubbleColor;
|
||||
@property (nonatomic, nullable) NSArray<UIColor *> *bubbleGradientColors;
|
||||
|
||||
@property (nonatomic) OWSDirectionalRectCorner sharpCorners;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSBubbleView.h"
|
||||
@ -40,10 +40,13 @@ UIRectCorner UIRectCornerForOWSDirectionalRectCorner(OWSDirectionalRectCorner co
|
||||
const CGFloat kOWSMessageCellCornerRadius_Large = 18;
|
||||
const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSBubbleView ()
|
||||
|
||||
@property (nonatomic) CAShapeLayer *maskLayer;
|
||||
@property (nonatomic) CAShapeLayer *shapeLayer;
|
||||
@property (nonatomic) CAGradientLayer *gradientLayer;
|
||||
|
||||
@property (nonatomic, readonly) NSMutableArray<id<OWSBubbleViewPartner>> *partnerViews;
|
||||
|
||||
@ -64,6 +67,11 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
|
||||
self.shapeLayer = [CAShapeLayer new];
|
||||
[self.layer addSublayer:self.shapeLayer];
|
||||
self.shapeLayer.hidden = YES;
|
||||
|
||||
self.gradientLayer = [CAGradientLayer new];
|
||||
[self.layer addSublayer:self.gradientLayer];
|
||||
self.gradientLayer.hidden = YES;
|
||||
|
||||
self.maskLayer = [CAShapeLayer new];
|
||||
self.layer.mask = self.maskLayer;
|
||||
@ -118,17 +126,22 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
|
||||
- (void)setBubbleColor:(nullable UIColor *)bubbleColor
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
_bubbleColor = bubbleColor;
|
||||
_bubbleGradientColors = nil;
|
||||
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
// Prevent the shape layer from animating changes.
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:YES];
|
||||
- (void)setBubbleGradientColors:(nullable NSArray<UIColor *> *)bubbleGradientColors
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
self.shapeLayer.fillColor = bubbleColor.CGColor;
|
||||
_bubbleColor = nil;
|
||||
_bubbleGradientColors = bubbleGradientColors;
|
||||
|
||||
[CATransaction commit];
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
- (void)setSharpCorners:(OWSDirectionalRectCorner)sharpCorners
|
||||
@ -146,6 +159,9 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
if (!self.shapeLayer) {
|
||||
return;
|
||||
}
|
||||
if (!self.gradientLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIBezierPath *bezierPath = [self maskPath];
|
||||
|
||||
@ -154,12 +170,32 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
[CATransaction setDisableActions:YES];
|
||||
|
||||
self.shapeLayer.fillColor = self.bubbleColor.CGColor;
|
||||
self.shapeLayer.hidden = self.bubbleColor == nil;
|
||||
self.shapeLayer.path = bezierPath.CGPath;
|
||||
|
||||
NSArray *bubbleGradientCGColors = self.bubbleGradientCGColors;
|
||||
self.gradientLayer.colors = bubbleGradientCGColors;
|
||||
self.gradientLayer.hidden = bubbleGradientCGColors.count < 1;
|
||||
// Currently this view only supports linear (or axial) gradients
|
||||
// from the top-left to bottom-right.
|
||||
self.gradientLayer.startPoint = CGPointMake(0, 1);
|
||||
self.gradientLayer.endPoint = CGPointMake(1, 0);
|
||||
self.gradientLayer.frame = self.bounds;
|
||||
|
||||
self.maskLayer.path = bezierPath.CGPath;
|
||||
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
- (NSArray *)bubbleGradientCGColors
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray new];
|
||||
for (UIColor *color in self.bubbleGradientColors) {
|
||||
[result addObject:(id)color.CGColor];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (UIBezierPath *)maskPath
|
||||
{
|
||||
return [self.class maskPathForSize:self.bounds.size sharpCorners:self.sharpCorners];
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#import "OWSContactAvatarBuilder.h"
|
||||
#import "OWSMessageBubbleView.h"
|
||||
#import "OWSMessageHeaderView.h"
|
||||
#import "OWSMessageHiddenView.h"
|
||||
#import "OWSMessageStickerView.h"
|
||||
#import "Signal-Swift.h"
|
||||
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMessageView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
//@class ContactShareViewModel;
|
||||
//@class OWSContact;
|
||||
//@class OWSLinkPreview;
|
||||
//@class OWSQuotedReplyModel;
|
||||
//@class StickerPackInfo;
|
||||
//@class TSAttachmentPointer;
|
||||
//@class TSAttachmentStream;
|
||||
//@class TSOutgoingMessage;
|
||||
|
||||
@protocol OWSMessageHiddenViewDelegate
|
||||
|
||||
//- (void)didTapImageViewItem:(id<ConversationViewItem>)viewItem
|
||||
// attachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
// imageView:(UIView *)imageView;
|
||||
//
|
||||
//- (void)didTapVideoViewItem:(id<ConversationViewItem>)viewItem
|
||||
// attachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
// imageView:(UIView *)imageView;
|
||||
//
|
||||
//- (void)didTapAudioViewItem:(id<ConversationViewItem>)viewItem attachmentStream:(TSAttachmentStream
|
||||
//*)attachmentStream;
|
||||
//
|
||||
//- (void)didTapTruncatedTextMessage:(id<ConversationViewItem>)conversationItem;
|
||||
//
|
||||
//- (void)didTapFailedIncomingAttachment:(id<ConversationViewItem>)viewItem;
|
||||
//
|
||||
//- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem quotedReply:(OWSQuotedReplyModel *)quotedReply;
|
||||
//- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem
|
||||
// quotedReply:(OWSQuotedReplyModel *)quotedReply
|
||||
// failedThumbnailDownloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer;
|
||||
//
|
||||
//- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem linkPreview:(OWSLinkPreview *)linkPreview;
|
||||
//
|
||||
//- (void)didTapContactShareViewItem:(id<ConversationViewItem>)viewItem;
|
||||
//
|
||||
//- (void)didTapSendMessageToContactShare:(ContactShareViewModel *)contactShare
|
||||
// NS_SWIFT_NAME(didTapSendMessage(toContactShare:));
|
||||
//- (void)didTapSendInviteToContactShare:(ContactShareViewModel *)contactShare
|
||||
// NS_SWIFT_NAME(didTapSendInvite(toContactShare:));
|
||||
//- (void)didTapShowAddToContactUIForContactShare:(ContactShareViewModel *)contactShare
|
||||
// NS_SWIFT_NAME(didTapShowAddToContactUI(forContactShare:));
|
||||
//
|
||||
//- (void)didTapStickerPack:(StickerPackInfo *)stickerPackInfo NS_SWIFT_NAME(didTapStickerPack(_:));
|
||||
//
|
||||
//@property (nonatomic, readonly, nullable) NSString *lastSearchedText;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSMessageHiddenView : OWSMessageView
|
||||
|
||||
@property (nonatomic, weak) id<OWSMessageHiddenViewDelegate> delegate;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,581 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSMessageHiddenView.h"
|
||||
#import "AttachmentUploadView.h"
|
||||
#import "ConversationViewItem.h"
|
||||
|
||||
//#import "OWSAudioMessageView.h"
|
||||
#import "OWSBubbleShapeView.h"
|
||||
#import "OWSBubbleView.h"
|
||||
|
||||
//#import "OWSContactShareButtonsView.h"
|
||||
//#import "OWSContactShareView.h"
|
||||
//#import "OWSGenericAttachmentView.h"
|
||||
#import "OWSLabel.h"
|
||||
#import "OWSMessageFooterView.h"
|
||||
|
||||
//#import "OWSMessageTextView.h"
|
||||
//#import "OWSQuotedMessageView.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSMessageHiddenView ()
|
||||
|
||||
@property (nonatomic) OWSBubbleView *bubbleView;
|
||||
|
||||
@property (nonatomic) UIStackView *vStackView;
|
||||
|
||||
@property (nonatomic) UIStackView *hStackView;
|
||||
|
||||
@property (nonatomic) UIImageView *iconView;
|
||||
|
||||
@property (nonatomic) UILabel *label;
|
||||
|
||||
//@property (nonatomic) UIView *senderNameContainer;
|
||||
//
|
||||
//@property (nonatomic) OWSMessageTextView *bodyTextView;
|
||||
//
|
||||
//@property (nonatomic, nullable) UIView *quotedMessageView;
|
||||
//
|
||||
//@property (nonatomic, nullable) UIView *bodyMediaView;
|
||||
//
|
||||
//@property (nonatomic) LinkPreviewView *linkPreviewView;
|
||||
//
|
||||
//// Should lazy-load expensive view contents (images, etc.).
|
||||
//// Should do nothing if view is already loaded.
|
||||
//@property (nonatomic, nullable) dispatch_block_t loadCellContentBlock;
|
||||
//// Should unload all expensive view contents (images, etc.).
|
||||
//@property (nonatomic, nullable) dispatch_block_t unloadCellContentBlock;
|
||||
|
||||
@property (nonatomic, nullable) NSMutableArray<NSLayoutConstraint *> *viewConstraints;
|
||||
|
||||
@property (nonatomic) OWSMessageFooterView *footerView;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSMessageHiddenView
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (OWSAttachmentDownloads *)attachmentDownloads
|
||||
{
|
||||
return SSKEnvironment.shared.attachmentDownloads;
|
||||
}
|
||||
|
||||
//- (TSAccountManager *)tsAccountManager
|
||||
//{
|
||||
// OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
//
|
||||
// return SSKEnvironment.shared.tsAccountManager;
|
||||
//}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
[self commontInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commontInit
|
||||
{
|
||||
// Ensure only called once.
|
||||
OWSAssertDebug(!self.vStackView);
|
||||
|
||||
_viewConstraints = [NSMutableArray new];
|
||||
|
||||
self.layoutMargins = UIEdgeInsetsZero;
|
||||
self.userInteractionEnabled = YES;
|
||||
|
||||
self.bubbleView = [OWSBubbleView new];
|
||||
self.bubbleView.layoutMargins = UIEdgeInsetsZero;
|
||||
[self addSubview:self.bubbleView];
|
||||
[self.bubbleView autoPinEdgesToSuperviewEdges];
|
||||
|
||||
self.iconView = [UIImageView new];
|
||||
[self.iconView setContentHuggingHorizontalHigh];
|
||||
[self.iconView setCompressionResistanceHorizontalHigh];
|
||||
[self.iconView autoSetDimension:ALDimensionWidth toSize:self.iconSize];
|
||||
[self.iconView autoSetDimension:ALDimensionHeight toSize:self.iconSize];
|
||||
|
||||
self.label = [OWSLabel new];
|
||||
|
||||
self.hStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.iconView,
|
||||
self.label,
|
||||
]];
|
||||
self.hStackView.axis = UILayoutConstraintAxisHorizontal;
|
||||
self.hStackView.spacing = self.contentHSpacing;
|
||||
self.hStackView.alignment = UIStackViewAlignmentCenter;
|
||||
self.vStackView = [UIStackView new];
|
||||
self.vStackView.axis = UILayoutConstraintAxisVertical;
|
||||
|
||||
self.footerView = [OWSMessageFooterView new];
|
||||
}
|
||||
|
||||
#pragma mark - Convenience Accessors
|
||||
|
||||
- (OWSMessageCellType)cellType
|
||||
{
|
||||
return self.viewItem.messageCellType;
|
||||
}
|
||||
|
||||
- (TSMessage *)message
|
||||
{
|
||||
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
|
||||
|
||||
return (TSMessage *)self.viewItem.interaction;
|
||||
}
|
||||
|
||||
- (BOOL)isIncoming
|
||||
{
|
||||
return self.viewItem.interaction.interactionType == OWSInteractionType_IncomingMessage;
|
||||
}
|
||||
|
||||
- (BOOL)isOutgoing
|
||||
{
|
||||
return self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage;
|
||||
}
|
||||
|
||||
- (BOOL)perMessageExpirationHasExpired
|
||||
{
|
||||
return self.viewItem.perMessageExpirationHasExpired;
|
||||
}
|
||||
|
||||
#pragma mark - Load
|
||||
|
||||
- (void)configureViews
|
||||
{
|
||||
OWSAssertDebug(self.conversationStyle);
|
||||
OWSAssertDebug(self.viewItem);
|
||||
OWSAssertDebug(self.viewItem.interaction);
|
||||
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
|
||||
|
||||
[self.bubbleView addSubview:self.vStackView];
|
||||
[self.viewConstraints addObjectsFromArray:[self.vStackView autoPinEdgesToSuperviewEdges]];
|
||||
NSMutableArray<UIView *> *textViews = [NSMutableArray new];
|
||||
// TODO:
|
||||
[self addProgressViewsIfNecessary:self.overlayHostView shouldShowDownloadProgress:NO];
|
||||
|
||||
[self configureIconView];
|
||||
[self configureLabel];
|
||||
[textViews addObject:self.hStackView];
|
||||
|
||||
if (self.viewItem.shouldHideFooter) {
|
||||
// Do nothing.
|
||||
} else {
|
||||
[self.footerView configureWithConversationViewItem:self.viewItem
|
||||
conversationStyle:self.conversationStyle
|
||||
isIncoming:self.isIncoming
|
||||
isOverlayingMedia:NO
|
||||
isOutsideBubble:NO];
|
||||
[textViews addObject:self.footerView];
|
||||
}
|
||||
|
||||
[self insertAnyTextViewsIntoStackView:textViews];
|
||||
|
||||
CGSize bubbleSize = [self measureSize];
|
||||
[self.viewConstraints addObjectsFromArray:@[
|
||||
[self autoSetDimension:ALDimensionWidth toSize:bubbleSize.width],
|
||||
]];
|
||||
|
||||
[self updateBubbleColor];
|
||||
|
||||
[self configureBubbleRounding];
|
||||
}
|
||||
|
||||
- (CGFloat)iconSize
|
||||
{
|
||||
return 24.f;
|
||||
}
|
||||
|
||||
- (OWSDirectionalRectCorner)sharpCorners
|
||||
{
|
||||
OWSDirectionalRectCorner sharpCorners = 0;
|
||||
|
||||
if (!self.viewItem.isFirstInCluster) {
|
||||
sharpCorners = sharpCorners
|
||||
| (self.isIncoming ? OWSDirectionalRectCornerTopLeading : OWSDirectionalRectCornerTopTrailing);
|
||||
}
|
||||
|
||||
if (!self.viewItem.isLastInCluster) {
|
||||
sharpCorners = sharpCorners
|
||||
| (self.isIncoming ? OWSDirectionalRectCornerBottomLeading : OWSDirectionalRectCornerBottomTrailing);
|
||||
}
|
||||
|
||||
return sharpCorners;
|
||||
}
|
||||
|
||||
- (OWSDirectionalRectCorner)sharpCornersForQuotedMessage
|
||||
{
|
||||
if (self.viewItem.senderName) {
|
||||
return OWSDirectionalRectCornerAllCorners;
|
||||
} else {
|
||||
return self.sharpCorners | OWSDirectionalRectCornerBottomLeading | OWSDirectionalRectCornerBottomTrailing;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configureBubbleRounding
|
||||
{
|
||||
self.bubbleView.sharpCorners = self.sharpCorners;
|
||||
}
|
||||
|
||||
- (void)updateBubbleColor
|
||||
{
|
||||
if (self.perMessageExpirationHasExpired) {
|
||||
self.bubbleView.bubbleColor = UIColor.ows_gray15Color;
|
||||
} else if (self.isIncoming) {
|
||||
self.bubbleView.bubbleGradientColors = @[
|
||||
[UIColor colorWithRGBHex:0xD0D1D1],
|
||||
UIColor.ows_gray05Color,
|
||||
];
|
||||
} else {
|
||||
self.bubbleView.bubbleGradientColors = @[
|
||||
[UIColor colorWithRGBHex:0x1C7ECD],
|
||||
UIColor.ows_signalBlueColor,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)contentHSpacing
|
||||
{
|
||||
return 6.f;
|
||||
}
|
||||
|
||||
- (UIColor *)contentForegroundColor
|
||||
{
|
||||
if (self.perMessageExpirationHasExpired) {
|
||||
return UIColor.ows_gray60Color;
|
||||
} else if (self.isIncoming) {
|
||||
return UIColor.ows_gray90Color;
|
||||
} else {
|
||||
return UIColor.ows_whiteColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)overlayHostView
|
||||
{
|
||||
return self.hStackView;
|
||||
}
|
||||
|
||||
- (UIColor *)overlayBackgroundColor
|
||||
{
|
||||
if (self.perMessageExpirationHasExpired) {
|
||||
return UIColor.ows_gray15Color;
|
||||
} else if (self.isIncoming) {
|
||||
return UIColor.ows_gray05Color;
|
||||
} else {
|
||||
return UIColor.ows_signalBlueColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasBottomFooter
|
||||
{
|
||||
return !self.viewItem.shouldHideFooter;
|
||||
}
|
||||
|
||||
- (BOOL)insertAnyTextViewsIntoStackView:(NSArray<UIView *> *)textViews
|
||||
{
|
||||
if (textViews.count < 1) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
UIStackView *textStackView = [[UIStackView alloc] initWithArrangedSubviews:textViews];
|
||||
textStackView.axis = UILayoutConstraintAxisVertical;
|
||||
textStackView.spacing = self.textViewVSpacing;
|
||||
textStackView.layoutMarginsRelativeArrangement = YES;
|
||||
textStackView.layoutMargins = UIEdgeInsetsMake(self.conversationStyle.textInsetTop,
|
||||
self.conversationStyle.textInsetHorizontal,
|
||||
self.conversationStyle.textInsetBottom,
|
||||
self.conversationStyle.textInsetHorizontal);
|
||||
[self.vStackView addArrangedSubview:textStackView];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CGFloat)textViewVSpacing
|
||||
{
|
||||
return 2.f;
|
||||
}
|
||||
|
||||
#pragma mark - Load / Unload
|
||||
|
||||
- (void)loadContent
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
- (void)unloadContent
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
#pragma mark - Subviews
|
||||
|
||||
- (void)configureLabel
|
||||
{
|
||||
OWSAssertDebug(self.label);
|
||||
|
||||
self.label.textColor = self.contentForegroundColor;
|
||||
self.label.font = UIFont.ows_dynamicTypeSubheadlineFont.ows_mediumWeight;
|
||||
self.label.text
|
||||
= (self.perMessageExpirationHasExpired ? NSLocalizedString(@"PER_MESSAGE_EXPIRATION_VIEWED",
|
||||
@"Label for messages with per-message expiration indicating that "
|
||||
@"user has viewed the message's contents.")
|
||||
: NSLocalizedString(@"PER_MESSAGE_EXPIRATION_TAP_TO_VIEW",
|
||||
@"Label for messages with per-message expiration indicating that "
|
||||
@"user can tap to view the message's contents."));
|
||||
self.label.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
|
||||
- (void)configureIconView
|
||||
{
|
||||
OWSAssertDebug(self.iconView);
|
||||
|
||||
[self.iconView setTemplateImageName:(self.perMessageExpirationHasExpired ? @"play-outline-24" : @"play-filled-24")
|
||||
tintColor:self.contentForegroundColor];
|
||||
}
|
||||
|
||||
- (void)addProgressViewsIfNecessary:(UIView *)bodyMediaView shouldShowDownloadProgress:(BOOL)shouldShowDownloadProgress
|
||||
{
|
||||
if (self.viewItem.attachmentStream) {
|
||||
[self addUploadViewIfNecessary:bodyMediaView];
|
||||
} else if (self.viewItem.attachmentPointer) {
|
||||
[self addDownloadViewIfNecessary:bodyMediaView shouldShowDownloadProgress:(BOOL)shouldShowDownloadProgress];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addUploadViewIfNecessary:(UIView *)bodyMediaView
|
||||
{
|
||||
OWSAssertDebug(self.viewItem.attachmentStream);
|
||||
|
||||
if (!self.isOutgoing) {
|
||||
return;
|
||||
}
|
||||
if (self.viewItem.attachmentStream.isUploaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
AttachmentUploadView *uploadView = [[AttachmentUploadView alloc] initWithAttachment:self.viewItem.attachmentStream];
|
||||
[self.bubbleView addSubview:uploadView];
|
||||
[uploadView autoPinEdgesToSuperviewEdges];
|
||||
[uploadView setContentHuggingLow];
|
||||
[uploadView setCompressionResistanceLow];
|
||||
}
|
||||
|
||||
- (void)addDownloadViewIfNecessary:(UIView *)bodyMediaView shouldShowDownloadProgress:(BOOL)shouldShowDownloadProgress
|
||||
{
|
||||
OWSAssertDebug(self.viewItem.attachmentPointer);
|
||||
|
||||
switch (self.viewItem.attachmentPointer.state) {
|
||||
case TSAttachmentPointerStateFailed:
|
||||
[self addTapToRetryView:self.overlayHostView];
|
||||
return;
|
||||
case TSAttachmentPointerStateEnqueued:
|
||||
case TSAttachmentPointerStateDownloading:
|
||||
break;
|
||||
}
|
||||
switch (self.viewItem.attachmentPointer.pointerType) {
|
||||
case TSAttachmentPointerTypeRestoring:
|
||||
// TODO: Show "restoring" indicator and possibly progress.
|
||||
return;
|
||||
case TSAttachmentPointerTypeUnknown:
|
||||
case TSAttachmentPointerTypeIncoming:
|
||||
break;
|
||||
}
|
||||
if (!shouldShowDownloadProgress) {
|
||||
return;
|
||||
}
|
||||
NSString *_Nullable uniqueId = self.viewItem.attachmentPointer.uniqueId;
|
||||
if (uniqueId.length < 1) {
|
||||
OWSFailDebug(@"Missing uniqueId.");
|
||||
return;
|
||||
}
|
||||
if ([self.attachmentDownloads downloadProgressForAttachmentId:uniqueId] == nil) {
|
||||
OWSFailDebug(@"Missing download progress.");
|
||||
return;
|
||||
}
|
||||
|
||||
UIView *overlayView = [UIView new];
|
||||
overlayView.backgroundColor = [self.overlayBackgroundColor colorWithAlphaComponent:0.5];
|
||||
[bodyMediaView addSubview:overlayView];
|
||||
[overlayView autoPinEdgesToSuperviewEdges];
|
||||
[overlayView setContentHuggingLow];
|
||||
[overlayView setCompressionResistanceLow];
|
||||
|
||||
MediaDownloadView *downloadView =
|
||||
[[MediaDownloadView alloc] initWithAttachmentId:uniqueId radius:self.conversationStyle.maxMessageWidth * 0.1f];
|
||||
bodyMediaView.layer.opacity = 0.5f;
|
||||
[self.bubbleView addSubview:downloadView];
|
||||
[downloadView autoPinEdgesToSuperviewEdges];
|
||||
[downloadView setContentHuggingLow];
|
||||
[downloadView setCompressionResistanceLow];
|
||||
}
|
||||
|
||||
// TODO:
|
||||
- (void)addTapToRetryView:(UIView *)bodyMediaView
|
||||
{
|
||||
OWSAssertDebug(self.viewItem.attachmentPointer);
|
||||
|
||||
// Hide the body media view, replace with "tap to retry" indicator.
|
||||
|
||||
UILabel *label = [UILabel new];
|
||||
label.text = NSLocalizedString(
|
||||
@"ATTACHMENT_DOWNLOADING_STATUS_FAILED", @"Status label when an attachment download has failed.");
|
||||
label.font = UIFont.ows_dynamicTypeBodyFont;
|
||||
label.textColor = Theme.secondaryColor;
|
||||
label.numberOfLines = 0;
|
||||
label.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.backgroundColor = self.overlayBackgroundColor;
|
||||
[bodyMediaView addSubview:label];
|
||||
[label autoPinEdgesToSuperviewMargins];
|
||||
[label setContentHuggingLow];
|
||||
[label setCompressionResistanceLow];
|
||||
}
|
||||
|
||||
#pragma mark - Measurement
|
||||
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
OWSAssertDebug(self.conversationStyle);
|
||||
OWSAssertDebug(self.conversationStyle.maxMessageWidth > 0);
|
||||
|
||||
CGFloat hMargins = self.conversationStyle.textInsetHorizontal * 2;
|
||||
const int maxTextWidth
|
||||
= (int)floor(self.conversationStyle.maxMessageWidth - (hMargins + self.contentHSpacing + self.iconSize));
|
||||
[self configureLabel];
|
||||
CGSize result = CGSizeCeil([self.label sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]);
|
||||
result.width += self.contentHSpacing + self.iconSize;
|
||||
result.width = MIN(result.width, self.conversationStyle.maxMessageWidth);
|
||||
result.height = MAX(result.height, self.iconSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CGSize)measureSize
|
||||
{
|
||||
OWSAssertDebug(self.conversationStyle);
|
||||
OWSAssertDebug(self.conversationStyle.viewWidth > 0);
|
||||
OWSAssertDebug(self.viewItem);
|
||||
OWSAssertDebug([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
|
||||
|
||||
CGSize cellSize = CGSizeZero;
|
||||
|
||||
[self configureBubbleRounding];
|
||||
|
||||
NSMutableArray<NSValue *> *textViewSizes = [NSMutableArray new];
|
||||
|
||||
CGSize contentSize = self.contentSize;
|
||||
[textViewSizes addObject:[NSValue valueWithCGSize:contentSize]];
|
||||
|
||||
if (self.hasBottomFooter) {
|
||||
CGSize footerSize = [self.footerView measureWithConversationViewItem:self.viewItem];
|
||||
footerSize.width = MIN(footerSize.width, self.conversationStyle.maxMessageWidth);
|
||||
[textViewSizes addObject:[NSValue valueWithCGSize:footerSize]];
|
||||
}
|
||||
|
||||
if (textViewSizes.count > 0) {
|
||||
CGSize groupSize = [self sizeForTextViewGroup:textViewSizes];
|
||||
cellSize.width = MAX(cellSize.width, groupSize.width);
|
||||
cellSize.height += groupSize.height;
|
||||
}
|
||||
|
||||
// Make sure the bubble is always wide enough to complete it's bubble shape.
|
||||
cellSize.width = MAX(cellSize.width, self.bubbleView.minWidth);
|
||||
|
||||
OWSAssertDebug(cellSize.width > 0 && cellSize.height > 0);
|
||||
|
||||
cellSize = CGSizeCeil(cellSize);
|
||||
|
||||
OWSAssertDebug(cellSize.width <= self.conversationStyle.maxMessageWidth);
|
||||
cellSize.width = MIN(cellSize.width, self.conversationStyle.maxMessageWidth);
|
||||
|
||||
return cellSize;
|
||||
}
|
||||
|
||||
- (CGSize)sizeForTextViewGroup:(NSArray<NSValue *> *)textViewSizes
|
||||
{
|
||||
OWSAssertDebug(textViewSizes);
|
||||
OWSAssertDebug(textViewSizes.count > 0);
|
||||
OWSAssertDebug(self.conversationStyle);
|
||||
OWSAssertDebug(self.conversationStyle.maxMessageWidth > 0);
|
||||
|
||||
CGSize result = CGSizeZero;
|
||||
for (NSValue *size in textViewSizes) {
|
||||
result.width = MAX(result.width, size.CGSizeValue.width);
|
||||
result.height += size.CGSizeValue.height;
|
||||
}
|
||||
result.height += self.textViewVSpacing * (textViewSizes.count - 1);
|
||||
result.height += (self.conversationStyle.textInsetTop + self.conversationStyle.textInsetBottom);
|
||||
result.width += self.conversationStyle.textInsetHorizontal * 2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[NSLayoutConstraint deactivateConstraints:self.viewConstraints];
|
||||
self.viewConstraints = [NSMutableArray new];
|
||||
|
||||
self.delegate = nil;
|
||||
|
||||
self.label.text = nil;
|
||||
self.iconView.image = nil;
|
||||
|
||||
self.bubbleView.bubbleColor = nil;
|
||||
[self.bubbleView clearPartnerViews];
|
||||
|
||||
for (UIView *subview in self.bubbleView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
[self.footerView removeFromSuperview];
|
||||
[self.footerView prepareForReuse];
|
||||
|
||||
for (UIView *subview in self.vStackView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
for (UIView *subview in self.subviews) {
|
||||
if (subview != self.bubbleView) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Gestures
|
||||
|
||||
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
|
||||
{
|
||||
OWSAssertDebug(self.delegate);
|
||||
|
||||
if (sender.state != UIGestureRecognizerStateRecognized) {
|
||||
OWSLogVerbose(@"Ignoring tap on message: %@", self.viewItem.interaction.debugDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
}
|
||||
|
||||
- (OWSMessageGestureLocation)gestureLocationForLocation:(CGPoint)locationInMessageBubble
|
||||
{
|
||||
return OWSMessageGestureLocation_Default;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -132,6 +132,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
@property (nonatomic, readonly, nullable) StickerInfo *stickerInfo;
|
||||
@property (nonatomic, readonly, nullable) TSAttachmentStream *stickerAttachment;
|
||||
@property (nonatomic, readonly) BOOL isFailedSticker;
|
||||
@property (nonatomic, readonly) BOOL perMessageExpirationHasExpired;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *systemMessageText;
|
||||
|
||||
|
||||
@ -99,6 +99,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
@property (nonatomic, nullable) StickerInfo *stickerInfo;
|
||||
@property (nonatomic, nullable) TSAttachmentStream *stickerAttachment;
|
||||
@property (nonatomic) BOOL isFailedSticker;
|
||||
@property (nonatomic) BOOL perMessageExpirationHasExpired;
|
||||
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
|
||||
@property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer;
|
||||
@property (nonatomic, nullable) ContactShareViewModel *contactShare;
|
||||
@ -171,6 +172,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
self.stickerInfo = nil;
|
||||
self.stickerAttachment = nil;
|
||||
self.isFailedSticker = NO;
|
||||
self.perMessageExpirationHasExpired = NO;
|
||||
self.contactShare = nil;
|
||||
self.systemMessageText = nil;
|
||||
self.authorConversationColorName = nil;
|
||||
@ -379,6 +381,17 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)setPerMessageExpirationHasExpired:(BOOL)perMessageExpirationHasExpired
|
||||
{
|
||||
if (_perMessageExpirationHasExpired == perMessageExpirationHasExpired) {
|
||||
return;
|
||||
}
|
||||
|
||||
_perMessageExpirationHasExpired = perMessageExpirationHasExpired;
|
||||
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)clearCachedLayoutState
|
||||
{
|
||||
self.cachedCellSize = nil;
|
||||
@ -673,7 +686,30 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
TSMessage *message = (TSMessage *)self.interaction;
|
||||
|
||||
if (message.hasPerMessageExpiration) {
|
||||
self.messageCellType = OWSMessageCellType_PerMessageExpiration;
|
||||
if (transaction.transitional_yapReadTransaction) {
|
||||
NSArray<TSAttachment *> *mediaAttachments =
|
||||
[message mediaAttachmentsWithTransaction:transaction.transitional_yapReadTransaction];
|
||||
// TODO: We currently only support single attachments for messages
|
||||
// with per-message expiration.
|
||||
TSAttachment *_Nullable mediaAttachment = mediaAttachments.firstObject;
|
||||
if ([mediaAttachment isKindOfClass:[TSAttachmentPointer class]]) {
|
||||
self.messageCellType = OWSMessageCellType_PerMessageExpiration;
|
||||
self.perMessageExpirationHasExpired = message.perMessageExpirationHasExpired;
|
||||
self.attachmentPointer = (TSAttachmentPointer *)mediaAttachment;
|
||||
return;
|
||||
} else if ([mediaAttachment isKindOfClass:[TSAttachmentStream class]]) {
|
||||
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)mediaAttachment;
|
||||
if (attachmentStream.isValidVisualMedia) {
|
||||
self.messageCellType = OWSMessageCellType_PerMessageExpiration;
|
||||
self.perMessageExpirationHasExpired = message.perMessageExpirationHasExpired;
|
||||
self.attachmentStream = attachmentStream;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OWSFailDebug(@"Invalid media for message with per-message expiration.");
|
||||
self.messageCellType = OWSMessageCellType_GenericAttachment;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1637,6 +1637,12 @@
|
||||
/* Navbar title for for PDF view. */
|
||||
"PDF_VIEW_TITLE" = "PDF";
|
||||
|
||||
/* Label for messages with per-message expiration indicating that user can tap to view the message's contents. */
|
||||
"PER_MESSAGE_EXPIRATION_TAP_TO_VIEW" = "Tap to view";
|
||||
|
||||
/* Label for messages with per-message expiration indicating that user has viewed the message's contents. */
|
||||
"PER_MESSAGE_EXPIRATION_VIEWED" = "Viewed";
|
||||
|
||||
/* A format for a label showing an example phone number. Embeds {{the example phone number}}. */
|
||||
"PHONE_NUMBER_EXAMPLE_FORMAT" = "Example: %@";
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user