mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Toast view when tapped message doesn't exist, mark remotely sourced.
This commit is contained in:
parent
9ab447a3db
commit
8829cdfb4b
|
@ -436,6 +436,7 @@
|
|||
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */; };
|
||||
4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; };
|
||||
4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; };
|
||||
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; };
|
||||
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
|
||||
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; };
|
||||
4CC0B59C20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */; };
|
||||
|
@ -1118,6 +1119,7 @@
|
|||
4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContactDiscoveryOperationTest.swift; path = contact/ContactDiscoveryOperationTest.swift; sourceTree = "<group>"; };
|
||||
4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = "<group>"; };
|
||||
4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = "<group>"; };
|
||||
4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; };
|
||||
4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = "<group>"; };
|
||||
4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||
|
@ -2229,6 +2231,7 @@
|
|||
450D19111F85236600970622 /* RemoteVideoView.h */,
|
||||
450D19121F85236600970622 /* RemoteVideoView.m */,
|
||||
4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */,
|
||||
4CA5F792211E1F06008C2708 /* Toast.swift */,
|
||||
);
|
||||
name = Views;
|
||||
path = views;
|
||||
|
@ -3390,6 +3393,7 @@
|
|||
34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */,
|
||||
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,
|
||||
45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */,
|
||||
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */,
|
||||
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,
|
||||
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
|
||||
457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */,
|
||||
|
|
23
Signal/Images.xcassets/ic_broken_link.imageset/Contents.json
vendored
Normal file
23
Signal/Images.xcassets/ic_broken_link.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "broken-link-16@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "broken-link-16@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "broken-link-16@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
Signal/Images.xcassets/ic_broken_link.imageset/broken-link-16@1x.png
vendored
Normal file
BIN
Signal/Images.xcassets/ic_broken_link.imageset/broken-link-16@1x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 259 B |
BIN
Signal/Images.xcassets/ic_broken_link.imageset/broken-link-16@2x.png
vendored
Normal file
BIN
Signal/Images.xcassets/ic_broken_link.imageset/broken-link-16@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 488 B |
BIN
Signal/Images.xcassets/ic_broken_link.imageset/broken-link-16@3x.png
vendored
Normal file
BIN
Signal/Images.xcassets/ic_broken_link.imageset/broken-link-16@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 B |
|
@ -13,10 +13,13 @@
|
|||
#import <SignalMessaging/UIView+OWS.h>
|
||||
#import <SignalServiceKit/TSAttachmentStream.h>
|
||||
#import <SignalServiceKit/TSMessage.h>
|
||||
#import <SignalServiceKit/TSQuotedMessage.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
const CGFloat kRemotelySourcedContentGlyphLength = 16;
|
||||
const CGFloat kRemotelySourcedContentRowMargin = 4;
|
||||
const CGFloat kRemotelySourcedContentRowSpacing = 3;
|
||||
|
||||
@interface OWSQuotedMessageView ()
|
||||
|
||||
@property (nonatomic, readonly) OWSQuotedReplyModel *quotedMessage;
|
||||
|
@ -29,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@property (nonatomic, readonly) UILabel *quotedAuthorLabel;
|
||||
@property (nonatomic, readonly) UILabel *quotedTextLabel;
|
||||
@property (nonatomic, readonly) UILabel *quoteContentSourceLabel;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -97,6 +101,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
_quotedAuthorLabel = [UILabel new];
|
||||
_quotedTextLabel = [UILabel new];
|
||||
_quoteContentSourceLabel = [UILabel new];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -143,6 +148,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return 4.f;
|
||||
}
|
||||
|
||||
- (UIColor *)quoteBubbleBackgroundColor
|
||||
{
|
||||
return [self.conversationStyle quotedReplyBubbleColorWithIsIncoming:!self.isOutgoing];
|
||||
}
|
||||
|
||||
- (void)createContents
|
||||
{
|
||||
// Ensure only called once.
|
||||
|
@ -179,7 +189,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
maskLayer.path = bezierPath.CGPath;
|
||||
}];
|
||||
innerBubbleView.layer.mask = maskLayer;
|
||||
innerBubbleView.backgroundColor = [self.conversationStyle quotedReplyBubbleColorWithIsIncoming:!self.isOutgoing];
|
||||
innerBubbleView.backgroundColor = self.quoteBubbleBackgroundColor;
|
||||
[self addSubview:innerBubbleView];
|
||||
[innerBubbleView autoPinLeadingToSuperviewMarginWithInset:self.bubbleHMargin];
|
||||
[innerBubbleView autoPinTrailingToSuperviewMarginWithInset:self.bubbleHMargin];
|
||||
|
@ -189,8 +199,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
UIStackView *hStackView = [UIStackView new];
|
||||
hStackView.axis = UILayoutConstraintAxisHorizontal;
|
||||
hStackView.spacing = self.hSpacing;
|
||||
[innerBubbleView addSubview:hStackView];
|
||||
[hStackView ows_autoPinToSuperviewEdges];
|
||||
|
||||
UIView *stripeView = [UIView new];
|
||||
stripeView.backgroundColor = [self.conversationStyle quotedReplyStripeColorWithIsIncoming:!self.isOutgoing];
|
||||
|
@ -278,6 +286,48 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[emptyView setContentHuggingHigh];
|
||||
[emptyView autoSetDimension:ALDimensionWidth toSize:0.f];
|
||||
}
|
||||
|
||||
UIStackView *quoteSourceWrapper = [[UIStackView alloc] initWithArrangedSubviews:@[ hStackView ]];
|
||||
quoteSourceWrapper.axis = UILayoutConstraintAxisVertical;
|
||||
|
||||
if (self.quotedMessage.isRemotelySourced) {
|
||||
[quoteSourceWrapper addArrangedSubview:[self buildRemoteContentSourceView]];
|
||||
}
|
||||
|
||||
[innerBubbleView addSubview:quoteSourceWrapper];
|
||||
[quoteSourceWrapper ows_autoPinToSuperviewEdges];
|
||||
}
|
||||
|
||||
- (UIView *)buildRemoteContentSourceView
|
||||
{
|
||||
UIImage *glyphImage =
|
||||
[[UIImage imageNamed:@"ic_broken_link"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
OWSAssert(glyphImage);
|
||||
OWSAssert(CGSizeEqualToSize(
|
||||
CGSizeMake(kRemotelySourcedContentGlyphLength, kRemotelySourcedContentGlyphLength), glyphImage.size));
|
||||
UIImageView *glyphView = [[UIImageView alloc] initWithImage:glyphImage];
|
||||
glyphView.tintColor = Theme.secondaryColor;
|
||||
[glyphView
|
||||
autoSetDimensionsToSize:CGSizeMake(kRemotelySourcedContentGlyphLength, kRemotelySourcedContentGlyphLength)];
|
||||
|
||||
UILabel *label = [self configureQuoteContentSourceLabel];
|
||||
UIStackView *sourceRow = [[UIStackView alloc] initWithArrangedSubviews:@[ glyphView, label ]];
|
||||
sourceRow.axis = UILayoutConstraintAxisHorizontal;
|
||||
sourceRow.alignment = UIStackViewAlignmentCenter;
|
||||
// TODO verify spacing w/ design
|
||||
sourceRow.spacing = kRemotelySourcedContentRowSpacing;
|
||||
sourceRow.layoutMarginsRelativeArrangement = YES;
|
||||
|
||||
const CGFloat leftMargin = 8;
|
||||
sourceRow.layoutMargins = UIEdgeInsetsMake(kRemotelySourcedContentRowMargin,
|
||||
leftMargin,
|
||||
kRemotelySourcedContentRowMargin,
|
||||
kRemotelySourcedContentRowMargin);
|
||||
|
||||
UIColor *backgroundColor = [UIColor.whiteColor colorWithAlphaComponent:0.4];
|
||||
[sourceRow addBackgroundViewWithBackgroundColor:backgroundColor];
|
||||
|
||||
return sourceRow;
|
||||
}
|
||||
|
||||
- (void)didTapFailedThumbnailDownload:(UITapGestureRecognizer *)gestureRecognizer
|
||||
|
@ -367,6 +417,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return self.quotedTextLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)configureQuoteContentSourceLabel
|
||||
{
|
||||
OWSAssert(self.quoteContentSourceLabel);
|
||||
|
||||
self.quoteContentSourceLabel.font = UIFont.ows_dynamicTypeFootnoteFont;
|
||||
self.quoteContentSourceLabel.textColor = Theme.primaryColor;
|
||||
self.quoteContentSourceLabel.text = NSLocalizedString(@"QUOTED_REPLY_CONTENT_FROM_REMOTE_SOURCE",
|
||||
@"Footer label that appears below quoted messages when the quoted content was note derived locally. When the "
|
||||
@"local user doesn't have a copy of the message being quoted, e.g. if it had since been deleted, we instead "
|
||||
@"show the content specified by the sender.");
|
||||
|
||||
return self.quoteContentSourceLabel;
|
||||
}
|
||||
|
||||
- (nullable NSString *)fileTypeForSnippet
|
||||
{
|
||||
// TODO: Are we going to use the filename? For all mimetypes?
|
||||
|
@ -487,6 +551,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
textHeight += textSize.height;
|
||||
}
|
||||
|
||||
if (self.quotedMessage.isRemotelySourced) {
|
||||
UILabel *quoteContentSourceLabel = [self configureQuoteContentSourceLabel];
|
||||
CGSize textSize = CGSizeCeil([quoteContentSourceLabel sizeThatFits:CGSizeMake(maxTextWidth, CGFLOAT_MAX)]);
|
||||
CGFloat sourceStackViewHeight = MAX(kRemotelySourcedContentGlyphLength, textSize.height);
|
||||
|
||||
textWidth
|
||||
= MAX(textWidth, textSize.width + kRemotelySourcedContentGlyphLength + kRemotelySourcedContentRowSpacing);
|
||||
result.height += kRemotelySourcedContentRowMargin * 2 + sourceStackViewHeight;
|
||||
}
|
||||
|
||||
textWidth = MIN(textWidth, maxTextWidth);
|
||||
result.width += textWidth;
|
||||
result.height += MAX(textHeight, thumbnailHeight);
|
||||
|
|
|
@ -2433,7 +2433,7 @@ typedef enum : NSUInteger {
|
|||
}];
|
||||
|
||||
if (!quotedInteraction || !groupIndex) {
|
||||
DDLogError(@"%@ Couldn't find message quoted in quoted reply.", self.logTag);
|
||||
[self presentMissingQuotedReplyToast];
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2534,7 +2534,8 @@ typedef enum : NSUInteger {
|
|||
|
||||
__block OWSQuotedReplyModel *quotedReply;
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
quotedReply = [OWSQuotedReplyModel quotedReplyForConversationViewItem:conversationItem transaction:transaction];
|
||||
quotedReply = [OWSQuotedReplyModel quotedReplyForSendingWithConversationViewItem:conversationItem
|
||||
transaction:transaction];
|
||||
}];
|
||||
|
||||
if (![quotedReply isKindOfClass:[OWSQuotedReplyModel class]]) {
|
||||
|
@ -5292,6 +5293,23 @@ typedef enum : NSUInteger {
|
|||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Toast
|
||||
|
||||
- (void)presentMissingQuotedReplyToast
|
||||
{
|
||||
DDLogInfo(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__);
|
||||
|
||||
NSString *toastText = NSLocalizedString(@"QUOTED_REPLY_MISSING_ORIGINAL_MESSAGE",
|
||||
@"Toast alert text shown when tapping on a quoted message which we cannot scroll to, because the local copy of "
|
||||
@"the message doesn't exist.");
|
||||
|
||||
ToastController *toastController = [[ToastController alloc] initWithText:toastText];
|
||||
|
||||
CGFloat bottomInset = 10 + self.collectionView.contentInset.bottom + self.view.layoutMargins.bottom;
|
||||
|
||||
[toastController presentToastViewFromBottomOfView:self.view inset:bottomInset];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)presentViewController:(UIViewController *)viewController
|
||||
|
|
|
@ -539,7 +539,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
|
||||
if (message.quotedMessage) {
|
||||
self.quotedReply =
|
||||
[[OWSQuotedReplyModel alloc] initWithQuotedMessage:message.quotedMessage transaction:transaction];
|
||||
[OWSQuotedReplyModel quotedReplyWithQuotedMessage:message.quotedMessage transaction:transaction];
|
||||
|
||||
if (self.quotedReply.body.length > 0) {
|
||||
self.displayableQuotedText =
|
||||
|
|
|
@ -1995,7 +1995,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
isGroupThread:thread.isGroupThread
|
||||
transaction:transaction
|
||||
conversationStyle:conversationStyle];
|
||||
quotedMessage = [[OWSQuotedReplyModel quotedReplyForConversationViewItem:viewItem transaction:transaction] buildQuotedMessage];
|
||||
quotedMessage = [
|
||||
[OWSQuotedReplyModel quotedReplyForSendingWithConversationViewItem:viewItem transaction:transaction]
|
||||
buildQuotedMessageForSending];
|
||||
} else {
|
||||
TSOutgoingMessage *_Nullable messageToQuote = [self createFakeOutgoingMessage:thread
|
||||
messageBody:quotedMessageBodyWIndex
|
||||
|
@ -2012,7 +2014,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
isGroupThread:thread.isGroupThread
|
||||
transaction:transaction
|
||||
conversationStyle:conversationStyle];
|
||||
quotedMessage = [[OWSQuotedReplyModel quotedReplyForConversationViewItem:viewItem transaction:transaction] buildQuotedMessage];
|
||||
quotedMessage = [
|
||||
[OWSQuotedReplyModel quotedReplyForSendingWithConversationViewItem:viewItem transaction:transaction]
|
||||
buildQuotedMessageForSending];
|
||||
}
|
||||
OWSAssert(quotedMessage);
|
||||
|
||||
|
|
142
Signal/src/views/Toast.swift
Normal file
142
Signal/src/views/Toast.swift
Normal file
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol ToastViewDelegate: class {
|
||||
func didTapToastView(_ toastView: ToastView)
|
||||
func didSwipeToastView(_ toastView: ToastView)
|
||||
}
|
||||
|
||||
class ToastView: UIView {
|
||||
|
||||
var text: String? {
|
||||
get {
|
||||
return label.text
|
||||
}
|
||||
set {
|
||||
label.text = newValue
|
||||
}
|
||||
}
|
||||
weak var delegate: ToastViewDelegate?
|
||||
|
||||
private let label: UILabel
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
override init(frame: CGRect) {
|
||||
label = UILabel()
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.cornerRadius = 4
|
||||
self.backgroundColor = Theme.toastBackgroundColor
|
||||
self.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
|
||||
|
||||
label.textAlignment = .center
|
||||
label.textColor = Theme.toastForegroundColor
|
||||
label.font = UIFont.ows_dynamicTypeBody
|
||||
label.numberOfLines = 0
|
||||
self.addSubview(label)
|
||||
label.autoPinEdgesToSuperviewMargins()
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
|
||||
self.addGestureRecognizer(tapGesture)
|
||||
|
||||
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipe(gesture:)))
|
||||
self.addGestureRecognizer(swipeGesture)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: Gestures
|
||||
|
||||
@objc
|
||||
func didTap(gesture: UITapGestureRecognizer) {
|
||||
self.delegate?.didTapToastView(self)
|
||||
}
|
||||
|
||||
@objc
|
||||
func didSwipe(gesture: UISwipeGestureRecognizer) {
|
||||
self.delegate?.didSwipeToastView(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
class ToastController: NSObject, ToastViewDelegate {
|
||||
|
||||
private let toastView: ToastView
|
||||
private var isDismissing: Bool
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
@objc
|
||||
required init(text: String) {
|
||||
toastView = ToastView()
|
||||
toastView.text = text
|
||||
isDismissing = false
|
||||
|
||||
super.init()
|
||||
|
||||
toastView.delegate = self
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@objc
|
||||
func presentToastView(fromBottomOfView view: UIView, inset: CGFloat) {
|
||||
Logger.debug("\(logTag) in \(#function)")
|
||||
toastView.alpha = 0
|
||||
view.addSubview(toastView)
|
||||
toastView.setCompressionResistanceHigh()
|
||||
toastView.autoPinEdge(.bottom, to: .bottom, of: view, withOffset: -inset)
|
||||
toastView.autoPinWidthToSuperview(withMargin: 24)
|
||||
|
||||
UIView.animate(withDuration: 0.1) {
|
||||
self.toastView.alpha = 1
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
|
||||
// intentional strong reference to self.
|
||||
// As with an AlertController, the caller likely expects toast to
|
||||
// be presented and dismissed without maintaining a strong reference to ToastController
|
||||
self.dismissToastView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ToastViewDelegate
|
||||
|
||||
func didTapToastView(_ toastView: ToastView) {
|
||||
Logger.debug("\(logTag) in \(#function)")
|
||||
self.dismissToastView()
|
||||
}
|
||||
|
||||
func didSwipeToastView(_ toastView: ToastView) {
|
||||
Logger.debug("\(logTag) in \(#function)")
|
||||
self.dismissToastView()
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
func dismissToastView() {
|
||||
Logger.debug("\(logTag) in \(#function)")
|
||||
|
||||
guard !isDismissing else {
|
||||
return
|
||||
}
|
||||
isDismissing = true
|
||||
UIView.animate(withDuration: 0.1,
|
||||
animations: {
|
||||
self.toastView.alpha = 0
|
||||
},
|
||||
completion: { (_) in
|
||||
self.toastView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1607,6 +1607,12 @@
|
|||
/* message header label when quoting yourself */
|
||||
"QUOTED_REPLY_AUTHOR_INDICATOR_YOURSELF" = "Replying to Yourself";
|
||||
|
||||
/* Footer label that appears below quoted messages when the quoted content was note derived locally. When the local user doesn't have a copy of the message being quoted, e.g. if it had since been deleted, we instead show the content specified by the sender. */
|
||||
"QUOTED_REPLY_CONTENT_FROM_REMOTE_SOURCE" = "Original message not found.";
|
||||
|
||||
/* Toast alert text shown when tapping on a quoted message which we cannot scroll to, because the local copy of the message doesn't exist. */
|
||||
"QUOTED_REPLY_MISSING_ORIGINAL_MESSAGE" = "Original message not found.";
|
||||
|
||||
/* Indicates this message is a quoted reply to an attachment of unknown type. */
|
||||
"QUOTED_REPLY_TYPE_ATTACHMENT" = "Attachment";
|
||||
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <SignalServiceKit/TSQuotedMessage.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ConversationViewItem;
|
||||
@class TSAttachmentPointer;
|
||||
@class TSAttachmentStream;
|
||||
@class TSMessage;
|
||||
@class TSQuotedMessage;
|
||||
@class YapDatabaseReadTransaction;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// View model which has already fetched any attachments.
|
||||
@interface OWSQuotedReplyModel : NSObject
|
||||
|
||||
|
@ -23,6 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// This property should be set IFF we are quoting a text message
|
||||
// or attachment with caption.
|
||||
@property (nullable, nonatomic, readonly) NSString *body;
|
||||
@property (nonatomic, readonly) BOOL isRemotelySourced;
|
||||
|
||||
#pragma mark - Attachments
|
||||
|
||||
|
@ -33,27 +35,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic, readonly, nullable) NSString *sourceFilename;
|
||||
@property (nonatomic, readonly, nullable) UIImage *thumbnailImage;
|
||||
|
||||
// Convenience initializer for building an outgoing quoted reply preview, before it's sent
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(NSString *_Nullable)body
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachment; //TODO quotedAttachmentStream?
|
||||
|
||||
// Convenience initializer for building an outgoing quoted reply preview, before it's sent
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(NSString *_Nullable)body
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
// Used for persisted quoted replies, both incoming and outgoing.
|
||||
- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
+ (instancetype)quotedReplyWithQuotedMessage:(TSQuotedMessage *)quotedMessage
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
// Builds a not-yet-sent QuotedReplyModel
|
||||
+ (nullable instancetype)quotedReplyForConversationViewItem:(ConversationViewItem *)conversationItem
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
+ (nullable instancetype)quotedReplyForSendingWithConversationViewItem:(ConversationViewItem *)conversationItem
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
- (TSQuotedMessage *)buildQuotedMessage;
|
||||
- (TSQuotedMessage *)buildQuotedMessageForSending;
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,43 +16,64 @@
|
|||
#import <SignalServiceKit/TSQuotedMessage.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSQuotedReplyModel ()
|
||||
|
||||
@property (nonatomic, readonly) TSQuotedMessageContentSource bodySource;
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(nullable NSString *)body
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
thumbnailAttachmentPointer:(nullable TSAttachmentPointer *)thumbnailAttachmentPointer
|
||||
thumbnailDownloadFailed:(BOOL)thumbnailDownloadFailed NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
// View Model which has already fetched any thumbnail attachment.
|
||||
@implementation OWSQuotedReplyModel
|
||||
|
||||
#pragma mark - Initializers
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(NSString *_Nullable)body
|
||||
body:(nullable NSString *)body
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
thumbnailAttachmentPointer:(nullable TSAttachmentPointer *)thumbnailAttachmentPointer
|
||||
thumbnailDownloadFailed:(BOOL)thumbnailDownloadFailed
|
||||
{
|
||||
return [self initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:body
|
||||
thumbnailImage:attachmentStream.thumbnailImage
|
||||
contentType:attachmentStream.contentType
|
||||
sourceFilename:attachmentStream.sourceFilename
|
||||
attachmentStream:attachmentStream
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_timestamp = timestamp;
|
||||
_authorId = authorId;
|
||||
_body = body;
|
||||
_bodySource = bodySource;
|
||||
_thumbnailImage = thumbnailImage;
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
_attachmentStream = attachmentStream;
|
||||
_thumbnailAttachmentPointer = thumbnailAttachmentPointer;
|
||||
_thumbnailDownloadFailed = thumbnailDownloadFailed;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(NSString *_Nullable)body
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage;
|
||||
{
|
||||
return [self initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:body
|
||||
thumbnailImage:thumbnailImage
|
||||
contentType:nil
|
||||
sourceFilename:nil
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
}
|
||||
#pragma mark - Factory Methods
|
||||
|
||||
- (instancetype)initWithQuotedMessage:(TSQuotedMessage *)quotedMessage
|
||||
transaction:(YapDatabaseReadTransaction *)transaction
|
||||
+ (instancetype)quotedReplyWithQuotedMessage:(TSQuotedMessage *)quotedMessage
|
||||
transaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
OWSAssert(quotedMessage.quotedAttachments.count <= 1);
|
||||
OWSAttachmentInfo *attachmentInfo = quotedMessage.quotedAttachments.firstObject;
|
||||
|
@ -82,57 +103,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
return [self initWithTimestamp:quotedMessage.timestamp
|
||||
authorId:quotedMessage.authorId
|
||||
body:quotedMessage.body
|
||||
thumbnailImage:thumbnailImage
|
||||
contentType:attachmentInfo.contentType
|
||||
sourceFilename:attachmentInfo.sourceFilename
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:attachmentPointer
|
||||
thumbnailDownloadFailed:thumbnailDownloadFailed];
|
||||
return [[self alloc] initWithTimestamp:quotedMessage.timestamp
|
||||
authorId:quotedMessage.authorId
|
||||
body:quotedMessage.body
|
||||
bodySource:quotedMessage.bodySource
|
||||
thumbnailImage:thumbnailImage
|
||||
contentType:attachmentInfo.contentType
|
||||
sourceFilename:attachmentInfo.sourceFilename
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:attachmentPointer
|
||||
thumbnailDownloadFailed:thumbnailDownloadFailed];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(nullable NSString *)body
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
thumbnailAttachmentPointer:(nullable TSAttachmentPointer *)thumbnailAttachmentPointer
|
||||
thumbnailDownloadFailed:(BOOL)thumbnailDownloadFailed
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_timestamp = timestamp;
|
||||
_authorId = authorId;
|
||||
_body = body;
|
||||
_thumbnailImage = thumbnailImage;
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
_attachmentStream = attachmentStream;
|
||||
_thumbnailAttachmentPointer = thumbnailAttachmentPointer;
|
||||
_thumbnailDownloadFailed = thumbnailDownloadFailed;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (TSQuotedMessage *)buildQuotedMessage
|
||||
{
|
||||
NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[];
|
||||
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:self.timestamp
|
||||
authorId:self.authorId
|
||||
body:self.body
|
||||
quotedAttachmentsForSending:attachments];
|
||||
}
|
||||
|
||||
+ (nullable instancetype)quotedReplyForConversationViewItem:(ConversationViewItem *)conversationItem
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
+ (nullable instancetype)quotedReplyForSendingWithConversationViewItem:(ConversationViewItem *)conversationItem
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
{
|
||||
OWSAssert(conversationItem);
|
||||
OWSAssert(transaction);
|
||||
|
@ -167,11 +151,16 @@
|
|||
// because the QuotedReplyViewModel has some hardcoded assumptions that only quoted attachments have
|
||||
// thumbnails. Until we address that we want to be consistent about neither showing nor sending the
|
||||
// contactShare avatar in the quoted reply.
|
||||
return [[OWSQuotedReplyModel alloc] initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:[@"👤 " stringByAppendingString:contactShare.displayName]
|
||||
thumbnailImage:nil];
|
||||
|
||||
return [[self alloc] initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:[@"👤 " stringByAppendingString:contactShare.displayName]
|
||||
bodySource:TSQuotedMessageContentSourceLocal
|
||||
thumbnailImage:nil
|
||||
contentType:nil
|
||||
sourceFilename:nil
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
}
|
||||
|
||||
NSString *_Nullable quotedText = message.body;
|
||||
|
@ -234,11 +223,35 @@
|
|||
hasText = YES;
|
||||
}
|
||||
|
||||
return [[OWSQuotedReplyModel alloc] initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:quotedText
|
||||
attachmentStream:quotedAttachment];
|
||||
return [[self alloc] initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:quotedText
|
||||
bodySource:TSQuotedMessageContentSourceLocal
|
||||
thumbnailImage:quotedAttachment.thumbnailImage
|
||||
contentType:quotedAttachment.contentType
|
||||
sourceFilename:quotedAttachment.sourceFilename
|
||||
attachmentStream:quotedAttachment
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (TSQuotedMessage *)buildQuotedMessageForSending
|
||||
{
|
||||
NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[];
|
||||
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:self.timestamp
|
||||
authorId:self.authorId
|
||||
body:self.body
|
||||
quotedAttachmentsForSending:attachments];
|
||||
}
|
||||
|
||||
- (BOOL)isRemotelySourced
|
||||
{
|
||||
return self.bodySource == TSQuotedMessageContentSourceRemote;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -49,6 +49,11 @@ extern NSString *const ThemeDidChangeNotification;
|
|||
@property (class, readonly, nonatomic) UIColor *searchBarBackgroundColor;
|
||||
@property (class, readonly, nonatomic) UIBlurEffect *barBlurEffect;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@property (class, readonly, nonatomic) UIColor *toastForegroundColor;
|
||||
@property (class, readonly, nonatomic) UIColor *toastBackgroundColor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -154,6 +154,18 @@ NSString *const ThemeKeyThemeEnabled = @"ThemeKeyThemeEnabled";
|
|||
return Theme.backgroundColor;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (UIColor *)toastForegroundColor
|
||||
{
|
||||
return (Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor : UIColor.ows_whiteColor);
|
||||
}
|
||||
|
||||
+ (UIColor *)toastBackgroundColor
|
||||
{
|
||||
return (Theme.isDarkThemeEnabled ? UIColor.ows_dark60Color : UIColor.ows_light60Color);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -84,11 +84,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSDisappearingMessagesConfiguration *configuration =
|
||||
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId];
|
||||
uint32_t expiresInSeconds = (configuration.isEnabled ? configuration.durationSeconds : 0);
|
||||
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
|
||||
messageBody:text
|
||||
attachmentId:nil
|
||||
expiresInSeconds:expiresInSeconds
|
||||
quotedMessage:[quotedReplyModel buildQuotedMessage]];
|
||||
TSOutgoingMessage *message =
|
||||
[TSOutgoingMessage outgoingMessageInThread:thread
|
||||
messageBody:text
|
||||
attachmentId:nil
|
||||
expiresInSeconds:expiresInSeconds
|
||||
quotedMessage:[quotedReplyModel buildQuotedMessageForSending]];
|
||||
|
||||
[messageSender enqueueMessage:message success:successHandler failure:failureHandler];
|
||||
|
||||
|
@ -136,7 +137,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
expireStartedAt:0
|
||||
isVoiceMessage:[attachment isVoiceMessage]
|
||||
groupMetaMessage:TSGroupMessageUnspecified
|
||||
quotedMessage:[quotedReplyModel buildQuotedMessage]
|
||||
quotedMessage:[quotedReplyModel buildQuotedMessageForSending]
|
||||
contactShare:nil];
|
||||
|
||||
[messageSender enqueueAttachment:attachment.dataSource
|
||||
|
|
|
@ -44,6 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (nullable TSAttachment *)attachmentWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream;
|
||||
- (nullable NSString *)bodyTextWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
- (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
|
|
|
@ -226,6 +226,32 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
|
|||
}
|
||||
}
|
||||
|
||||
- (nullable NSString *)bodyTextWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
if (self.hasAttachments) {
|
||||
TSAttachment *_Nullable attachment = [self attachmentWithTransaction:transaction];
|
||||
|
||||
if ([OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType] &&
|
||||
[attachment isKindOfClass:TSAttachmentStream.class]) {
|
||||
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
|
||||
|
||||
NSData *_Nullable data = [NSData dataWithContentsOfFile:attachmentStream.filePath];
|
||||
if (data) {
|
||||
NSString *_Nullable text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
if (text) {
|
||||
return text.filterStringForDisplay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.body.length > 0) {
|
||||
return self.body.filterStringForDisplay;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// TODO: This method contains view-specific logic and probably belongs in NotificationsManager, not in SSK.
|
||||
- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
|
|
|
@ -41,10 +41,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, TSQuotedMessageContentSource) {
|
||||
TSQuotedMessageContentSourceUnknown,
|
||||
TSQuotedMessageContentSourceLocal,
|
||||
TSQuotedMessageContentSourceRemote
|
||||
};
|
||||
|
||||
@interface TSQuotedMessage : MTLModel
|
||||
|
||||
@property (nonatomic, readonly) uint64_t timestamp;
|
||||
@property (nonatomic, readonly) NSString *authorId;
|
||||
@property (nonatomic, readonly) TSQuotedMessageContentSource bodySource;
|
||||
|
||||
// This property should be set IFF we are quoting a text message
|
||||
// or attachment with caption.
|
||||
|
@ -80,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(NSString *_Nullable)body
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
receivedQuotedAttachmentInfos:(NSArray<OWSAttachmentInfo *> *)attachmentInfos;
|
||||
|
||||
// used when sending quoted messages
|
||||
|
|
|
@ -59,6 +59,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorId:(NSString *)authorId
|
||||
body:(NSString *_Nullable)body
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
receivedQuotedAttachmentInfos:(NSArray<OWSAttachmentInfo *> *)attachmentInfos
|
||||
{
|
||||
OWSAssert(timestamp > 0);
|
||||
|
@ -72,6 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_timestamp = timestamp;
|
||||
_authorId = authorId;
|
||||
_body = body;
|
||||
_bodySource = bodySource;
|
||||
_quotedAttachments = attachmentInfos;
|
||||
|
||||
return self;
|
||||
|
@ -93,6 +95,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_timestamp = timestamp;
|
||||
_authorId = authorId;
|
||||
_body = body;
|
||||
_bodySource = TSQuotedMessageContentSourceLocal;
|
||||
|
||||
NSMutableArray *attachmentInfos = [NSMutableArray new];
|
||||
for (TSAttachmentStream *attachmentStream in attachments) {
|
||||
|
@ -129,13 +132,32 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
NSString *authorId = [quoteProto author];
|
||||
|
||||
NSString *_Nullable body = nil;
|
||||
BOOL hasText = NO;
|
||||
BOOL hasAttachment = NO;
|
||||
if ([quoteProto hasText] && [quoteProto text].length > 0) {
|
||||
body = [quoteProto text];
|
||||
hasText = YES;
|
||||
TSQuotedMessageContentSource bodySource = TSQuotedMessageContentSourceUnknown;
|
||||
|
||||
// Prefer to generate the text snippet locally if available.
|
||||
TSMessage *_Nullable localRecord = (TSMessage *)[
|
||||
[TSInteraction interactionsWithTimestamp:quoteProto.id ofClass:TSMessage.class withTransaction:transaction]
|
||||
firstObject];
|
||||
|
||||
if (localRecord) {
|
||||
bodySource = TSQuotedMessageContentSourceLocal;
|
||||
|
||||
NSString *localText = [localRecord bodyTextWithTransaction:transaction];
|
||||
if (localText.length > 0) {
|
||||
body = localText;
|
||||
}
|
||||
}
|
||||
|
||||
if (body.length == 0) {
|
||||
if (quoteProto.text.length > 0) {
|
||||
bodySource = TSQuotedMessageContentSourceRemote;
|
||||
body = quoteProto.text;
|
||||
}
|
||||
}
|
||||
|
||||
OWSAssert(bodySource != TSQuotedMessageContentSourceUnknown);
|
||||
|
||||
NSMutableArray<OWSAttachmentInfo *> *attachmentInfos = [NSMutableArray new];
|
||||
for (SSKProtoDataMessageQuoteQuotedAttachment *quotedAttachment in quoteProto.attachments) {
|
||||
hasAttachment = YES;
|
||||
|
@ -180,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[attachmentInfos addObject:attachmentInfo];
|
||||
}
|
||||
|
||||
if (!hasText && !hasAttachment) {
|
||||
if (body.length == 0 && !hasAttachment) {
|
||||
OWSFail(@"%@ quoted message has neither text nor attachment", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
|
@ -188,6 +210,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return [[TSQuotedMessage alloc] initWithTimestamp:timestamp
|
||||
authorId:authorId
|
||||
body:body
|
||||
bodySource:bodySource
|
||||
receivedQuotedAttachmentInfos:attachmentInfos];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue