2014-07-10 16:11:09 +02:00
|
|
|
/*
|
|
|
|
* This is the source code of Telegram for iOS v. 1.1
|
|
|
|
* It is licensed under GNU GPL v. 2 or later.
|
|
|
|
* You should have received a copy of the license in this archive (see LICENSE).
|
|
|
|
*
|
|
|
|
* Copyright Peter Iakovlev, 2013.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#import "TGContentBubbleViewModel.h"
|
|
|
|
|
|
|
|
#import "TGMessage.h"
|
|
|
|
#import "TGUser.h"
|
2015-10-01 18:19:52 +02:00
|
|
|
#import "TGConversation.h"
|
|
|
|
#import "TGPeerIdAdapter.h"
|
2014-07-10 16:11:09 +02:00
|
|
|
|
|
|
|
#import "TGImageUtils.h"
|
|
|
|
#import "TGDateUtils.h"
|
|
|
|
#import "TGStringUtils.h"
|
|
|
|
|
|
|
|
#import "TGTelegraphConversationMessageAssetsSource.h"
|
|
|
|
#import "TGReusableLabel.h"
|
|
|
|
#import "TGModernConversationItem.h"
|
|
|
|
|
|
|
|
#import "TGTextMessageBackgroundViewModel.h"
|
|
|
|
#import "TGModernFlatteningViewModel.h"
|
|
|
|
#import "TGModernDateViewModel.h"
|
|
|
|
#import "TGModernClockProgressViewModel.h"
|
|
|
|
#import "TGModernTextViewModel.h"
|
|
|
|
|
|
|
|
#import "TGModernView.h"
|
|
|
|
|
|
|
|
#import "TGDoubleTapGestureRecognizer.h"
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
#import "TGReplyHeaderTextModel.h"
|
|
|
|
#import "TGReplyHeaderPhotoModel.h"
|
|
|
|
#import "TGReplyHeaderVideoModel.h"
|
|
|
|
#import "TGReplyHeaderAudioModel.h"
|
|
|
|
#import "TGReplyHeaderFileModel.h"
|
|
|
|
#import "TGReplyHeaderContactModel.h"
|
|
|
|
#import "TGReplyHeaderLocationModel.h"
|
|
|
|
#import "TGReplyHeaderStickerModel.h"
|
|
|
|
#import "TGReplyHeaderActionModel.h"
|
|
|
|
|
|
|
|
#import "TGPendingWebpageFooterModel.h"
|
|
|
|
#import "TGArticleWebpageFooterModel.h"
|
|
|
|
|
|
|
|
#import "TGMessageViewsViewModel.h"
|
|
|
|
|
|
|
|
bool debugShowMessageIds = false;
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
@interface TGContentBubbleViewModel () <UIGestureRecognizerDelegate, TGDoubleTapGestureRecognizerDelegate>
|
|
|
|
{
|
|
|
|
UITapGestureRecognizer *_unsentButtonTapRecognizer;
|
|
|
|
TGDoubleTapGestureRecognizer *_boundDoubleTapRecognizer;
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
TGModernImageViewModel *_broadcastIconModel;
|
|
|
|
CGPoint _itemPosition;
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation TGContentBubbleViewModel
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
+ (void)debugEnableShowMessageIds
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
debugShowMessageIds = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithMessage:(TGMessage *)message authorPeer:(id)authorPeer context:(TGModernViewContext *)context
|
|
|
|
{
|
|
|
|
self = [super initWithAuthorPeer:authorPeer context:context];
|
2014-07-10 16:11:09 +02:00
|
|
|
if (self != nil)
|
|
|
|
{
|
|
|
|
static TGTelegraphConversationMessageAssetsSource *assetsSource = nil;
|
|
|
|
|
|
|
|
static UIColor *incomingDateColor = nil;
|
|
|
|
static UIColor *outgoingDateColor = nil;
|
|
|
|
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^
|
|
|
|
{
|
|
|
|
assetsSource = [TGTelegraphConversationMessageAssetsSource instance];
|
|
|
|
|
|
|
|
incomingDateColor = UIColorRGBA(0x525252, 0.6f);
|
|
|
|
outgoingDateColor = UIColorRGBA(0x008c09, 0.8f);
|
|
|
|
});
|
|
|
|
|
|
|
|
_needsEditingCheckButton = true;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
bool isChannel = [authorPeer isKindOfClass:[TGConversation class]];
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
_mid = message.mid;
|
|
|
|
_incoming = !message.outgoing;
|
2015-10-01 18:19:52 +02:00
|
|
|
_incomingAppearance = _incoming || isChannel;
|
2014-07-10 16:11:09 +02:00
|
|
|
_deliveryState = message.deliveryState;
|
|
|
|
_read = !message.unread;
|
|
|
|
_date = (int32_t)message.date;
|
2015-10-01 18:19:52 +02:00
|
|
|
_messageViews = message.viewCount;
|
2014-07-10 16:11:09 +02:00
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
_backgroundModel = [[TGTextMessageBackgroundViewModel alloc] initWithType:_incomingAppearance ? TGTextMessageBackgroundIncoming : TGTextMessageBackgroundOutgoing];
|
2014-07-10 16:11:09 +02:00
|
|
|
_backgroundModel.blendMode = kCGBlendModeCopy;
|
|
|
|
_backgroundModel.skipDrawInContext = true;
|
|
|
|
[self addSubmodel:_backgroundModel];
|
|
|
|
|
|
|
|
_contentModel = [[TGModernFlatteningViewModel alloc] initWithContext:_context];
|
|
|
|
_contentModel.viewUserInteractionDisabled = true;
|
|
|
|
[self addSubmodel:_contentModel];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (authorPeer != nil)
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
NSString *title = @"";
|
|
|
|
if ([authorPeer isKindOfClass:[TGUser class]]) {
|
|
|
|
title = ((TGUser *)authorPeer).displayName;
|
|
|
|
} else if ([authorPeer isKindOfClass:[TGConversation class]]) {
|
|
|
|
title = ((TGConversation *)authorPeer).chatTitle;
|
|
|
|
}
|
|
|
|
_authorNameModel = [[TGModernTextViewModel alloc] initWithText:title font:[assetsSource messageAuthorNameFont]];
|
2014-07-10 16:11:09 +02:00
|
|
|
[_contentModel addSubmodel:_authorNameModel];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if ([authorPeer isKindOfClass:[TGUser class]]) {
|
|
|
|
_hasAvatar = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isChannel) {
|
|
|
|
[_backgroundModel setPartialMode:false];
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int daytimeVariant = 0;
|
|
|
|
NSString *dateText = [TGDateUtils stringForShortTime:(int)message.date daytimeVariant:&daytimeVariant];
|
2015-10-01 18:19:52 +02:00
|
|
|
if (debugShowMessageIds)
|
|
|
|
dateText = [[NSString alloc] initWithFormat:@"%d", message.mid];
|
|
|
|
_dateModel = [[TGModernDateViewModel alloc] initWithText:dateText textColor:_incomingAppearance ? incomingDateColor : outgoingDateColor daytimeVariant:daytimeVariant];
|
2014-07-10 16:11:09 +02:00
|
|
|
[_contentModel addSubmodel:_dateModel];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (message.isBroadcast)
|
|
|
|
{
|
|
|
|
_broadcastIconModel = [[TGModernImageViewModel alloc] initWithImage:[UIImage imageNamed:@"ModernMessageBroadcastIcon.png"]];
|
|
|
|
[_broadcastIconModel sizeToFit];
|
|
|
|
[_contentModel addSubmodel:_broadcastIconModel];
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
if (!_incoming)
|
|
|
|
{
|
|
|
|
static UIImage *checkPartialImage = nil;
|
|
|
|
static UIImage *checkCompleteImage = nil;
|
|
|
|
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^
|
|
|
|
{
|
|
|
|
checkPartialImage = [UIImage imageNamed:@"ModernMessageCheckmark2.png"];
|
|
|
|
checkCompleteImage = [UIImage imageNamed:@"ModernMessageCheckmark1.png"];
|
|
|
|
});
|
|
|
|
|
|
|
|
_checkFirstModel = [[TGModernImageViewModel alloc] initWithImage:checkCompleteImage];
|
|
|
|
_checkSecondModel = [[TGModernImageViewModel alloc] initWithImage:checkPartialImage];
|
|
|
|
|
|
|
|
if (_deliveryState == TGMessageDeliveryStatePending)
|
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
_progressModel = [[TGModernClockProgressViewModel alloc] initWithType:_incomingAppearance ? TGModernClockProgressTypeIncomingClock : TGModernClockProgressTypeOutgoingClock];
|
2014-07-10 16:11:09 +02:00
|
|
|
[self addSubmodel:_progressModel];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance) {
|
|
|
|
[self addSubmodel:_checkFirstModel];
|
|
|
|
[self addSubmodel:_checkSecondModel];
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
_checkFirstModel.alpha = 0.0f;
|
|
|
|
_checkSecondModel.alpha = 0.0f;
|
|
|
|
}
|
|
|
|
else if (_deliveryState == TGMessageDeliveryStateFailed)
|
|
|
|
{
|
|
|
|
[self addSubmodel:[self unsentButtonModel]];
|
|
|
|
}
|
|
|
|
else if (_deliveryState == TGMessageDeliveryStateDelivered)
|
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance) {
|
|
|
|
[_contentModel addSubmodel:_checkFirstModel];
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
_checkFirstEmbeddedInContent = true;
|
|
|
|
|
|
|
|
if (_read)
|
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance) {
|
|
|
|
[_contentModel addSubmodel:_checkSecondModel];
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
_checkSecondEmbeddedInContent = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance) {
|
|
|
|
[self addSubmodel:_checkSecondModel];
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
_checkSecondModel.alpha = 0.0f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
if (_messageViews != nil) {
|
|
|
|
_messageViewsModel = [[TGMessageViewsViewModel alloc] init];
|
|
|
|
_messageViewsModel.type = _incomingAppearance ? TGMessageViewsViewTypeIncoming : TGMessageViewsViewTypeOutgoing;
|
|
|
|
_messageViewsModel.count = _messageViews.viewCount;
|
|
|
|
[self addSubmodel:_messageViewsModel];
|
|
|
|
_messageViewsModel.hidden = _deliveryState != TGMessageDeliveryStateDelivered;
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setTemporaryHighlighted:(bool)temporaryHighlighted viewStorage:(TGModernViewStorage *)__unused viewStorage
|
|
|
|
{
|
|
|
|
if (temporaryHighlighted)
|
|
|
|
[_backgroundModel setHighlightedIfBound];
|
|
|
|
else
|
|
|
|
[_backgroundModel clearHighlight];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setAuthorNameColor:(UIColor *)authorNameColor
|
|
|
|
{
|
|
|
|
_authorNameModel.textColor = authorNameColor;
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
- (void)setForwardHeader:(id)forwardPeer messageId:(int32_t)messageId
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
|
|
|
if (_forwardedHeaderModel == nil)
|
|
|
|
{
|
|
|
|
static UIColor *incomingForwardColor = nil;
|
|
|
|
static UIColor *outgoingForwardColor = nil;
|
|
|
|
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^
|
|
|
|
{
|
|
|
|
incomingForwardColor = UIColorRGBA(0x007bff, 1.0f);
|
|
|
|
outgoingForwardColor = UIColorRGBA(0x00a516, 1.0f);
|
|
|
|
});
|
|
|
|
|
|
|
|
static NSRange formatNameRange;
|
|
|
|
|
|
|
|
static int localizationVersion = -1;
|
|
|
|
if (localizationVersion != TGLocalizedStaticVersion)
|
|
|
|
formatNameRange = [TGLocalized(@"Message.ForwardedMessage") rangeOfString:@"%@"];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
_forwardedMessageId = messageId;
|
|
|
|
NSString *authorName = @"";
|
|
|
|
if ([forwardPeer isKindOfClass:[TGUser class]]) {
|
|
|
|
_forwardedPeerId = ((TGUser *)forwardPeer).uid;
|
|
|
|
authorName = ((TGUser *)forwardPeer).displayName;
|
|
|
|
} else if ([forwardPeer isKindOfClass:[TGConversation class]]) {
|
|
|
|
_forwardedPeerId = ((TGConversation *)forwardPeer).conversationId;
|
|
|
|
authorName = ((TGConversation *)forwardPeer).chatTitle;
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
NSString *text = [[NSString alloc] initWithFormat:TGLocalizedStatic(@"Message.ForwardedMessage"), authorName];
|
|
|
|
|
|
|
|
_forwardedHeaderModel = [[TGModernTextViewModel alloc] initWithText:text font:[[TGTelegraphConversationMessageAssetsSource instance] messageForwardTitleFont]];
|
2015-10-01 18:19:52 +02:00
|
|
|
_forwardedHeaderModel.textColor = _incomingAppearance ? incomingForwardColor : outgoingForwardColor;
|
2014-07-10 16:11:09 +02:00
|
|
|
_forwardedHeaderModel.layoutFlags = TGReusableLabelLayoutMultiline;
|
|
|
|
if (formatNameRange.location != NSNotFound && authorName.length != 0)
|
|
|
|
{
|
|
|
|
NSArray *fontAttributes = [[NSArray alloc] initWithObjects:(__bridge id)[[TGTelegraphConversationMessageAssetsSource instance] messageForwardNameFont], (NSString *)kCTFontAttributeName, nil];
|
|
|
|
NSRange range = NSMakeRange(formatNameRange.location, authorName.length);
|
|
|
|
_forwardedHeaderModel.additionalAttributes = [[NSArray alloc] initWithObjects:[[NSValue alloc] initWithBytes:&range objCType:@encode(NSRange)], fontAttributes, nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
[_contentModel addSubmodel:_forwardedHeaderModel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
+ (TGReplyHeaderModel *)replyHeaderModelFromMessage:(TGMessage *)replyHeader peer:(id)peer incoming:(bool)incoming system:(bool)system
|
|
|
|
{
|
|
|
|
for (id attachment in replyHeader.mediaAttachments)
|
|
|
|
{
|
|
|
|
if ([attachment isKindOfClass:[TGImageMediaAttachment class]])
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderPhotoModel alloc] initWithPeer:peer imageMedia:(TGImageMediaAttachment *)attachment incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGVideoMediaAttachment class]])
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderVideoModel alloc] initWithPeer:peer videoMedia:(TGVideoMediaAttachment *)attachment incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]])
|
|
|
|
{
|
|
|
|
bool isSticker = false;
|
|
|
|
for (id attribute in ((TGDocumentMediaAttachment *)attachment).attributes)
|
|
|
|
{
|
|
|
|
if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]])
|
|
|
|
{
|
|
|
|
isSticker = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isSticker)
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderStickerModel alloc] initWithPeer:peer fileMedia:(TGDocumentMediaAttachment *)attachment incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderFileModel alloc] initWithPeer:peer fileMedia:(TGDocumentMediaAttachment *)attachment incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGLocationMediaAttachment class]])
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderLocationModel alloc] initWithPeer:peer latitude:((TGLocationMediaAttachment *)attachment).latitude longitude:((TGLocationMediaAttachment *)attachment).longitude incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGContactMediaAttachment class]])
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderContactModel alloc] initWithPeer:peer incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGAudioMediaAttachment class]])
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderAudioModel alloc] initWithPeer:peer audioMedia:(TGAudioMediaAttachment *)attachment incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGActionMediaAttachment class]])
|
|
|
|
{
|
|
|
|
return [[TGReplyHeaderActionModel alloc] initWithPeer:peer actionMedia:(TGActionMediaAttachment *)attachment incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [[TGReplyHeaderTextModel alloc] initWithPeer:peer text:replyHeader.text incoming:incoming system:system];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setReplyHeader:(TGMessage *)replyHeader peer:(id)peer
|
|
|
|
{
|
|
|
|
_replyHeaderModel = [TGContentBubbleViewModel replyHeaderModelFromMessage:replyHeader peer:peer incoming:_incomingAppearance system:false];
|
|
|
|
if (_replyHeaderModel != nil)
|
|
|
|
[_contentModel addSubmodel:_replyHeaderModel];
|
|
|
|
_replyMessageId = replyHeader.mid;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setWebPageFooter:(TGWebPageMediaAttachment *)webPage viewStorage:(TGModernViewStorage *)viewStorage
|
|
|
|
{
|
|
|
|
_webPage = webPage;
|
|
|
|
if (webPage.url.length == 0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
bool imageInText = true;
|
|
|
|
if ([webPage.pageType isEqualToString:@"photo"] || [webPage.pageType isEqualToString:@"video"])
|
|
|
|
imageInText = false;
|
|
|
|
|
|
|
|
_webPageFooterModel = [[TGArticleWebpageFooterModel alloc] initWithWithIncoming:_incomingAppearance webPage:webPage imageInText:imageInText hasViews:_messageViews != nil];
|
|
|
|
[_contentModel addSubmodel:_webPageFooterModel];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
{
|
|
|
|
[_webPageFooterModel bindSpecialViewsToContainer:_contentModel.boundView viewStorage:viewStorage atItemPosition:CGPointMake(_itemPosition.x + _webPageFooterModel.frame.origin.x, _itemPosition.y + _webPageFooterModel.frame.origin.y)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (UIView *)referenceViewForImageTransition
|
|
|
|
{
|
|
|
|
return [_webPageFooterModel referenceViewForImageTransition];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateMediaVisibility
|
|
|
|
{
|
|
|
|
[_webPageFooterModel setMediaVisible:[_context isMediaVisibleInMessage:_mid]];
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
- (TGModernImageViewModel *)unsentButtonModel
|
|
|
|
{
|
|
|
|
if (_unsentButtonModel == nil)
|
|
|
|
{
|
|
|
|
static UIImage *image = nil;
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^
|
|
|
|
{
|
|
|
|
image = [UIImage imageNamed:@"ModernMessageUnsentButton.png"];
|
|
|
|
});
|
|
|
|
|
|
|
|
_unsentButtonModel = [[TGModernImageViewModel alloc] initWithImage:image];
|
|
|
|
_unsentButtonModel.frame = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
|
|
|
|
_unsentButtonModel.extendedEdges = UIEdgeInsetsMake(6, 6, 6, 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _unsentButtonModel;
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
- (void)updateMessage:(TGMessage *)message viewStorage:(TGModernViewStorage *)viewStorage sizeUpdated:(bool *)sizeUpdated
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
[super updateMessage:message viewStorage:viewStorage sizeUpdated:sizeUpdated];
|
2014-07-10 16:11:09 +02:00
|
|
|
|
|
|
|
_mid = message.mid;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_messageViewsModel != nil) {
|
|
|
|
_messageViewsModel.count = message.viewCount.viewCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (id attachment in message.mediaAttachments)
|
|
|
|
{
|
|
|
|
if ([attachment isKindOfClass:[TGWebPageMediaAttachment class]])
|
|
|
|
{
|
|
|
|
if (_webPageFooterModel == nil)
|
|
|
|
{
|
|
|
|
if (![_webPage isEqual:attachment])
|
|
|
|
{
|
|
|
|
[self setWebPageFooter:attachment viewStorage:viewStorage];
|
|
|
|
if (sizeUpdated)
|
|
|
|
*sizeUpdated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (![_webPage isEqual:attachment])
|
|
|
|
{
|
|
|
|
[_contentModel removeSubmodel:_webPageFooterModel viewStorage:viewStorage];
|
|
|
|
_webPageFooterModel = nil;
|
|
|
|
|
|
|
|
[self setWebPageFooter:attachment viewStorage:viewStorage];
|
|
|
|
if (sizeUpdated)
|
|
|
|
*sizeUpdated = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
if (_deliveryState != message.deliveryState || (!_incoming && _read != !message.unread))
|
|
|
|
{
|
|
|
|
TGMessageViewModelLayoutConstants const *layoutConstants = TGGetMessageViewModelLayoutConstants();
|
|
|
|
|
|
|
|
TGMessageDeliveryState previousDeliveryState = _deliveryState;
|
|
|
|
_deliveryState = message.deliveryState;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_messageViewsModel != nil) {
|
|
|
|
_messageViewsModel.hidden = _deliveryState != TGMessageDeliveryStateDelivered;
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
bool previousRead = _read;
|
|
|
|
_read = !message.unread;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_date != (int32_t)message.date && !debugShowMessageIds)
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
|
|
|
_date = (int32_t)message.date;
|
|
|
|
|
|
|
|
int daytimeVariant = 0;
|
|
|
|
NSString *dateText = [TGDateUtils stringForShortTime:(int)message.date daytimeVariant:&daytimeVariant];
|
|
|
|
[_dateModel setText:dateText daytimeVariant:daytimeVariant];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_deliveryState == TGMessageDeliveryStateDelivered)
|
|
|
|
{
|
|
|
|
if (_progressModel != nil)
|
|
|
|
{
|
|
|
|
[self removeSubmodel:_progressModel viewStorage:viewStorage];
|
|
|
|
_progressModel = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
_checkFirstModel.alpha = 1.0f;
|
|
|
|
|
|
|
|
if (previousDeliveryState == TGMessageDeliveryStatePending && [_checkFirstModel boundView] != nil)
|
|
|
|
{
|
|
|
|
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
|
|
|
animation.fromValue = @(1.3f);
|
|
|
|
animation.toValue = @(1.0f);
|
|
|
|
animation.duration = 0.1;
|
|
|
|
animation.removedOnCompletion = true;
|
|
|
|
|
|
|
|
[[_checkFirstModel boundView].layer addAnimation:animation forKey:@"transform.scale"];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_read)
|
|
|
|
{
|
|
|
|
_checkSecondModel.alpha = 1.0f;
|
|
|
|
|
|
|
|
if (!previousRead && [_checkSecondModel boundView] != nil)
|
|
|
|
{
|
|
|
|
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
|
|
|
animation.fromValue = @(1.3f);
|
|
|
|
animation.toValue = @(1.0f);
|
|
|
|
animation.duration = 0.1;
|
|
|
|
animation.removedOnCompletion = true;
|
|
|
|
|
|
|
|
[[_checkSecondModel boundView].layer addAnimation:animation forKey:@"transform.scale"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_unsentButtonModel != nil)
|
|
|
|
{
|
|
|
|
[self removeSubmodel:_unsentButtonModel viewStorage:viewStorage];
|
|
|
|
_unsentButtonModel = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (_deliveryState == TGMessageDeliveryStateFailed)
|
|
|
|
{
|
|
|
|
if (_progressModel != nil)
|
|
|
|
{
|
|
|
|
[self removeSubmodel:_progressModel viewStorage:viewStorage];
|
|
|
|
_progressModel = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_checkFirstModel != nil)
|
|
|
|
{
|
|
|
|
if (_checkFirstEmbeddedInContent)
|
|
|
|
{
|
|
|
|
[_contentModel removeSubmodel:_checkFirstModel viewStorage:viewStorage];
|
|
|
|
[_contentModel setNeedsSubmodelContentsUpdate];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self removeSubmodel:_checkFirstModel viewStorage:viewStorage];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_checkSecondModel != nil)
|
|
|
|
{
|
|
|
|
if (_checkSecondEmbeddedInContent)
|
|
|
|
{
|
|
|
|
[_contentModel removeSubmodel:_checkSecondModel viewStorage:viewStorage];
|
|
|
|
[_contentModel setNeedsSubmodelContentsUpdate];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self removeSubmodel:_checkSecondModel viewStorage:viewStorage];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_unsentButtonModel == nil)
|
|
|
|
{
|
|
|
|
[self addSubmodel:[self unsentButtonModel]];
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
[_unsentButtonModel bindViewToContainer:[_contentModel boundView].superview viewStorage:viewStorage];
|
|
|
|
_unsentButtonModel.frame = CGRectOffset(_unsentButtonModel.frame, self.frame.size.width + _unsentButtonModel.frame.size.width, self.frame.size.height - _unsentButtonModel.frame.size.height - ((_collapseFlags & TGModernConversationItemCollapseBottom) ? 5 : 6));
|
|
|
|
|
|
|
|
_unsentButtonTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(unsentButtonTapGesture:)];
|
|
|
|
[[_unsentButtonModel boundView] addGestureRecognizer:_unsentButtonTapRecognizer];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self.frame.size.width > FLT_EPSILON)
|
|
|
|
{
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
{
|
|
|
|
[UIView animateWithDuration:0.2 animations:^
|
|
|
|
{
|
|
|
|
[self layoutForContainerSize:CGSizeMake(self.frame.size.width, 0.0f)];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self layoutForContainerSize:CGSizeMake(self.frame.size.width, 0.0f)];
|
|
|
|
}
|
|
|
|
|
|
|
|
[_contentModel updateSubmodelContentsIfNeeded];
|
|
|
|
}
|
|
|
|
else if (_deliveryState == TGMessageDeliveryStatePending)
|
|
|
|
{
|
|
|
|
if (_progressModel == nil)
|
|
|
|
{
|
|
|
|
CGFloat unsentOffset = 0.0f;
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance && previousDeliveryState == TGMessageDeliveryStateFailed)
|
2014-07-10 16:11:09 +02:00
|
|
|
unsentOffset = 29.0f;
|
|
|
|
|
|
|
|
_progressModel = [[TGModernClockProgressViewModel alloc] initWithType:TGModernClockProgressTypeOutgoingClock];
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_incomingAppearance) {
|
|
|
|
_progressModel.frame = CGRectMake(CGRectGetMaxX(_backgroundModel.frame) - _dateModel.frame.size.width - 27.0f - layoutConstants->rightInset - unsentOffset + (TGIsPad() ? 12.0f : 0.0f), _contentModel.frame.origin.y + _contentModel.frame.size.height - 17 + 1.0f, 15, 15);
|
|
|
|
} else {
|
|
|
|
_progressModel.frame = CGRectMake(CGRectGetMaxX(_backgroundModel.frame) - 23.0f - layoutConstants->rightInset - unsentOffset + (TGIsPad() ? 12.0f : 0.0f), _contentModel.frame.origin.y + _contentModel.frame.size.height - 17 + 1.0f, 15, 15);
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
[self addSubmodel:_progressModel];
|
|
|
|
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
{
|
|
|
|
[_progressModel bindViewToContainer:[_contentModel boundView].superview viewStorage:viewStorage];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[_contentModel removeSubmodel:_checkFirstModel viewStorage:viewStorage];
|
|
|
|
[_contentModel removeSubmodel:_checkSecondModel viewStorage:viewStorage];
|
|
|
|
_checkFirstEmbeddedInContent = false;
|
|
|
|
_checkSecondEmbeddedInContent = false;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance) {
|
|
|
|
if (![self containsSubmodel:_checkFirstModel])
|
|
|
|
{
|
|
|
|
[self addSubmodel:_checkFirstModel];
|
|
|
|
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
[_checkFirstModel bindViewToContainer:[_contentModel boundView].superview viewStorage:viewStorage];
|
|
|
|
}
|
|
|
|
if (![self containsSubmodel:_checkSecondModel])
|
|
|
|
{
|
|
|
|
[self addSubmodel:_checkSecondModel];
|
|
|
|
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
[_checkSecondModel bindViewToContainer:[_contentModel boundView].superview viewStorage:viewStorage];
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_checkFirstModel.alpha = 0.0f;
|
|
|
|
_checkSecondModel.alpha = 0.0f;
|
|
|
|
|
|
|
|
if (_unsentButtonModel != nil)
|
|
|
|
{
|
|
|
|
UIView<TGModernView> *unsentView = [_unsentButtonModel boundView];
|
|
|
|
if (unsentView != nil)
|
|
|
|
{
|
|
|
|
[unsentView removeGestureRecognizer:_unsentButtonTapRecognizer];
|
|
|
|
_unsentButtonTapRecognizer = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unsentView != nil)
|
|
|
|
{
|
|
|
|
[viewStorage allowResurrectionForOperations:^
|
|
|
|
{
|
|
|
|
[self removeSubmodel:_unsentButtonModel viewStorage:viewStorage];
|
|
|
|
|
|
|
|
UIView *restoredView = [viewStorage dequeueViewWithIdentifier:[unsentView viewIdentifier] viewStateIdentifier:[unsentView viewStateIdentifier]];
|
|
|
|
|
|
|
|
if (restoredView != nil)
|
|
|
|
{
|
|
|
|
[[_contentModel boundView].superview addSubview:restoredView];
|
|
|
|
|
|
|
|
[UIView animateWithDuration:0.2 animations:^
|
|
|
|
{
|
|
|
|
restoredView.frame = CGRectOffset(restoredView.frame, restoredView.frame.size.width + 9, 0.0f);
|
|
|
|
restoredView.alpha = 0.0f;
|
|
|
|
} completion:^(__unused BOOL finished)
|
|
|
|
{
|
|
|
|
[viewStorage enqueueView:restoredView];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self removeSubmodel:_unsentButtonModel viewStorage:viewStorage];
|
|
|
|
|
|
|
|
_unsentButtonModel = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self.frame.size.width > FLT_EPSILON)
|
|
|
|
{
|
|
|
|
if ([_contentModel boundView] != nil)
|
|
|
|
{
|
|
|
|
[UIView animateWithDuration:0.2 animations:^
|
|
|
|
{
|
|
|
|
[self layoutForContainerSize:CGSizeMake(self.frame.size.width, 0.0f)];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self layoutForContainerSize:CGSizeMake(self.frame.size.width, 0.0f)];
|
|
|
|
}
|
|
|
|
|
|
|
|
[_contentModel setNeedsSubmodelContentsUpdate];
|
|
|
|
[_contentModel updateSubmodelContentsIfNeeded];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateEditingState:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage animationDelay:(NSTimeInterval)animationDelay
|
|
|
|
{
|
|
|
|
bool editing = _context.editing;
|
|
|
|
if (editing != _editing)
|
|
|
|
{
|
|
|
|
[super updateEditingState:container viewStorage:viewStorage animationDelay:animationDelay];
|
|
|
|
|
|
|
|
_backgroundModel.viewUserInteractionDisabled = _editing;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_maybeRestructureStateModels:(TGModernViewStorage *)viewStorage
|
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incoming && [_contentModel boundView] == nil && !_incomingAppearance)
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
|
|
|
if (_deliveryState == TGMessageDeliveryStateDelivered)
|
|
|
|
{
|
|
|
|
if (!_checkFirstEmbeddedInContent)
|
|
|
|
{
|
|
|
|
if ([self.submodels containsObject:_checkFirstModel])
|
|
|
|
{
|
|
|
|
_checkFirstEmbeddedInContent = true;
|
|
|
|
|
|
|
|
[self removeSubmodel:_checkFirstModel viewStorage:viewStorage];
|
|
|
|
_checkFirstModel.frame = CGRectOffset(_checkFirstModel.frame, -_contentModel.frame.origin.x, -_contentModel.frame.origin.y);
|
|
|
|
[_contentModel addSubmodel:_checkFirstModel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_read && !_checkSecondEmbeddedInContent)
|
|
|
|
{
|
|
|
|
if ([self.submodels containsObject:_checkSecondModel])
|
|
|
|
{
|
|
|
|
_checkSecondEmbeddedInContent = true;
|
|
|
|
|
|
|
|
[self removeSubmodel:_checkSecondModel viewStorage:viewStorage];
|
|
|
|
_checkSecondModel.frame = CGRectOffset(_checkSecondModel.frame, -_contentModel.frame.origin.x, -_contentModel.frame.origin.y);
|
|
|
|
[_contentModel addSubmodel:_checkSecondModel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)bindSpecialViewsToContainer:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage atItemPosition:(CGPoint)itemPosition
|
|
|
|
{
|
|
|
|
[super bindSpecialViewsToContainer:container viewStorage:viewStorage atItemPosition:itemPosition];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
_itemPosition = itemPosition;
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
[_backgroundModel bindViewToContainer:container viewStorage:viewStorage];
|
|
|
|
[_backgroundModel boundView].frame = CGRectOffset([_backgroundModel boundView].frame, itemPosition.x, itemPosition.y);
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
[_replyHeaderModel bindSpecialViewsToContainer:container viewStorage:viewStorage atItemPosition:CGPointMake(itemPosition.x + _contentModel.frame.origin.x + _replyHeaderModel.frame.origin.x, itemPosition.y + _contentModel.frame.origin.y + _replyHeaderModel.frame.origin.y)];
|
|
|
|
|
|
|
|
[_webPageFooterModel bindSpecialViewsToContainer:container viewStorage:viewStorage atItemPosition:CGPointMake(itemPosition.x + _contentModel.frame.origin.x + _webPageFooterModel.frame.origin.x, itemPosition.y + _contentModel.frame.origin.y + _webPageFooterModel.frame.origin.y)];
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)bindViewToContainer:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage
|
|
|
|
{
|
|
|
|
[self _maybeRestructureStateModels:viewStorage];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
_itemPosition = CGPointZero;
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
[self updateEditingState:nil viewStorage:nil animationDelay:-1.0];
|
|
|
|
|
|
|
|
[super bindViewToContainer:container viewStorage:viewStorage];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
[_replyHeaderModel bindSpecialViewsToContainer:_contentModel.boundView viewStorage:viewStorage atItemPosition:CGPointMake(_replyHeaderModel.frame.origin.x, _replyHeaderModel.frame.origin.y)];
|
|
|
|
|
|
|
|
[_webPageFooterModel bindSpecialViewsToContainer:_contentModel.boundView viewStorage:viewStorage atItemPosition:CGPointMake(_webPageFooterModel.frame.origin.x, _webPageFooterModel.frame.origin.y)];
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
_boundDoubleTapRecognizer = [[TGDoubleTapGestureRecognizer alloc] initWithTarget:self action:@selector(messageDoubleTapGesture:)];
|
2015-10-01 18:19:52 +02:00
|
|
|
_boundDoubleTapRecognizer.cancelsTouchesInView = true;
|
2014-07-10 16:11:09 +02:00
|
|
|
_boundDoubleTapRecognizer.delegate = self;
|
|
|
|
|
|
|
|
UIView *backgroundView = [_backgroundModel boundView];
|
|
|
|
[backgroundView addGestureRecognizer:_boundDoubleTapRecognizer];
|
|
|
|
|
|
|
|
if (_unsentButtonModel != nil)
|
|
|
|
{
|
|
|
|
_unsentButtonTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(unsentButtonTapGesture:)];
|
|
|
|
[[_unsentButtonModel boundView] addGestureRecognizer:_unsentButtonTapRecognizer];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unbindView:(TGModernViewStorage *)viewStorage
|
|
|
|
{
|
|
|
|
UIView *backgroundView = [_backgroundModel boundView];
|
|
|
|
[backgroundView removeGestureRecognizer:_boundDoubleTapRecognizer];
|
|
|
|
_boundDoubleTapRecognizer.delegate = nil;
|
|
|
|
_boundDoubleTapRecognizer = nil;
|
|
|
|
|
|
|
|
if (_unsentButtonModel != nil)
|
|
|
|
{
|
|
|
|
[[_unsentButtonModel boundView] removeGestureRecognizer:_unsentButtonTapRecognizer];
|
|
|
|
_unsentButtonTapRecognizer = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
[super unbindView:viewStorage];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)relativeBoundsUpdated:(CGRect)bounds
|
|
|
|
{
|
|
|
|
[super relativeBoundsUpdated:bounds];
|
|
|
|
|
|
|
|
[_contentModel updateSubmodelContentsForVisibleRect:CGRectOffset(bounds, -_contentModel.frame.origin.x, -_contentModel.frame.origin.y)];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (CGRect)effectiveContentFrame
|
|
|
|
{
|
|
|
|
return _backgroundModel.frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)messageDoubleTapGesture:(TGDoubleTapGestureRecognizer *)recognizer
|
|
|
|
{
|
|
|
|
if (recognizer.state != UIGestureRecognizerStateBegan)
|
|
|
|
{
|
|
|
|
if (recognizer.state == UIGestureRecognizerStateRecognized)
|
|
|
|
{
|
|
|
|
CGPoint point = [recognizer locationInView:[_contentModel boundView]];
|
|
|
|
|
|
|
|
if (recognizer.longTapped)
|
|
|
|
[_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}];
|
|
|
|
else if (recognizer.doubleTapped)
|
|
|
|
[_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}];
|
2015-10-01 18:19:52 +02:00
|
|
|
else if (_forwardedHeaderModel && CGRectContainsPoint(_forwardedHeaderModel.frame, point)) {
|
|
|
|
if (TGPeerIdIsChannel(_forwardedPeerId)) {
|
|
|
|
[_context.companionHandle requestAction:@"peerAvatarTapped" options:@{@"peerId": @(_forwardedPeerId), @"messageId": @(_forwardedMessageId)}];
|
|
|
|
} else {
|
|
|
|
[_context.companionHandle requestAction:@"userAvatarTapped" options:@{@"uid": @((int32_t)_forwardedPeerId)}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (_replyHeaderModel && CGRectContainsPoint(_replyHeaderModel.frame, point))
|
|
|
|
[_context.companionHandle requestAction:@"navigateToMessage" options:@{@"mid": @(_replyMessageId), @"sourceMid": @(_mid)}];
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer didBeginAtPoint:(CGPoint)__unused point
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)gestureRecognizerDidFail:(TGDoubleTapGestureRecognizer *)__unused recognizer
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unsentButtonTapGesture:(UITapGestureRecognizer *)recognizer
|
|
|
|
{
|
|
|
|
if (recognizer.state == UIGestureRecognizerStateRecognized)
|
|
|
|
{
|
|
|
|
[_context.companionHandle requestAction:@"showUnsentMessageMenu" options:@{@"mid": @(_mid)}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (bool)gestureRecognizerShouldHandleLongTap:(TGDoubleTapGestureRecognizer *)__unused recognizer
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (int)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer shouldFailTap:(CGPoint)__unused point
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)doubleTapGestureRecognizerSingleTapped:(TGDoubleTapGestureRecognizer *)__unused recognizer
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setCollapseFlags:(int)collapseFlags
|
|
|
|
{
|
|
|
|
if (_collapseFlags != collapseFlags)
|
|
|
|
{
|
|
|
|
_collapseFlags = collapseFlags;
|
2015-10-01 18:19:52 +02:00
|
|
|
if ([_authorPeer isKindOfClass:[TGConversation class]]) {
|
|
|
|
[_backgroundModel setPartialMode:false];
|
|
|
|
} else {
|
|
|
|
[_backgroundModel setPartialMode:collapseFlags & TGModernConversationItemCollapseBottom];
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)layoutContentForHeaderHeight:(CGFloat)__unused headerHeight
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
- (CGSize)contentSizeForContainerSize:(CGSize)__unused containerSize needsContentsUpdate:(bool *)__unused needsContentsUpdate hasDate:(bool)__unused hasDate hasViews:(bool)__unused hasViews
|
2014-07-10 16:11:09 +02:00
|
|
|
{
|
|
|
|
return CGSizeZero;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)layoutForContainerSize:(CGSize)containerSize
|
|
|
|
{
|
2015-10-01 18:19:52 +02:00
|
|
|
bool isPost = _authorPeer != nil && [_authorPeer isKindOfClass:[TGConversation class]];
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
TGMessageViewModelLayoutConstants const *layoutConstants = TGGetMessageViewModelLayoutConstants();
|
|
|
|
|
|
|
|
bool isRTL = TGIsRTL();
|
|
|
|
|
|
|
|
CGFloat topSpacing = (_collapseFlags & TGModernConversationItemCollapseTop) ? layoutConstants->topInsetCollapsed : layoutConstants->topInset;
|
|
|
|
CGFloat bottomSpacing = (_collapseFlags & TGModernConversationItemCollapseBottom) ? layoutConstants->bottomInsetCollapsed : layoutConstants->bottomInset;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (isPost) {
|
|
|
|
topSpacing = layoutConstants->topPostInset;
|
|
|
|
bottomSpacing = layoutConstants->bottomPostInset;
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
CGSize contentContainerSize = CGSizeMake(MIN(420.0f, containerSize.width - 80.0f - (_hasAvatar ? 38.0f : 0.0f)), containerSize.height);
|
|
|
|
|
|
|
|
bool updateContents = false;
|
|
|
|
|
|
|
|
CGSize headerSize = CGSizeZero;
|
|
|
|
if (_authorNameModel != nil)
|
|
|
|
{
|
|
|
|
if (_authorNameModel.frame.size.width < FLT_EPSILON)
|
|
|
|
[_authorNameModel layoutForContainerSize:CGSizeMake(320.0f - 80.0f - (_hasAvatar ? 38.0f : 0.0f), 0.0f)];
|
|
|
|
|
|
|
|
CGRect authorNameFrame = _authorNameModel.frame;
|
|
|
|
authorNameFrame.origin = CGPointMake(1.0f, 1.0f + TGRetinaPixel);
|
|
|
|
_authorNameModel.frame = authorNameFrame;
|
|
|
|
|
|
|
|
headerSize = CGSizeMake(_authorNameModel.frame.size.width, _authorNameModel.frame.size.height + 1.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_forwardedHeaderModel != nil)
|
|
|
|
{
|
|
|
|
[_forwardedHeaderModel layoutForContainerSize:CGSizeMake(containerSize.width - 80.0f - (_hasAvatar ? 38.0f : 0.0f), containerSize.height)];
|
|
|
|
CGRect forwardedHeaderFrame = _forwardedHeaderModel.frame;
|
|
|
|
forwardedHeaderFrame.origin = CGPointMake(1.0f, headerSize.height + 1.0f);
|
|
|
|
_forwardedHeaderModel.frame = forwardedHeaderFrame;
|
|
|
|
|
|
|
|
headerSize.height += forwardedHeaderFrame.size.height;
|
|
|
|
headerSize.width = MAX(headerSize.width, forwardedHeaderFrame.size.width);
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_replyHeaderModel != nil)
|
|
|
|
{
|
|
|
|
bool updateContent = false;
|
|
|
|
[_replyHeaderModel layoutForContainerSize:CGSizeMake(containerSize.width - 80.0f - (_hasAvatar ? 38.0f : 0.0f), containerSize.height) updateContent:&updateContent];
|
|
|
|
if (updateContent)
|
|
|
|
updateContents = true;
|
|
|
|
CGRect replyHeaderFrame = _replyHeaderModel.frame;
|
|
|
|
replyHeaderFrame.origin = CGPointMake(1.0f, headerSize.height + 1.0f);
|
|
|
|
_replyHeaderModel.frame = replyHeaderFrame;
|
|
|
|
|
|
|
|
headerSize.height += replyHeaderFrame.size.height - 1.0f;
|
|
|
|
headerSize.width = MAX(headerSize.width, replyHeaderFrame.size.width);
|
|
|
|
}
|
|
|
|
|
|
|
|
CGFloat contentContainerWidth = contentContainerSize.width;
|
|
|
|
CGSize contentSize = CGSizeZero;
|
|
|
|
|
|
|
|
CGSize webPageSize = CGSizeZero;
|
|
|
|
if (_webPageFooterModel != nil)
|
|
|
|
{
|
|
|
|
if ([_webPageFooterModel preferWebpageSize])
|
|
|
|
{
|
|
|
|
[_webPageFooterModel layoutForContainerSize:CGSizeMake(contentContainerWidth, contentContainerSize.height) contentSize:CGSizeZero needsContentUpdate:&updateContents];
|
|
|
|
contentContainerWidth = _webPageFooterModel.frame.size.width;
|
|
|
|
|
|
|
|
contentSize = [self contentSizeForContainerSize:CGSizeMake(contentContainerWidth, contentContainerSize.height) needsContentsUpdate:&updateContents hasDate:_webPageFooterModel == nil hasViews:_messageViews != nil];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
contentSize = [self contentSizeForContainerSize:CGSizeMake(contentContainerWidth, contentContainerSize.height) needsContentsUpdate:&updateContents hasDate:_webPageFooterModel == nil hasViews:_messageViews != nil];
|
|
|
|
|
|
|
|
[_webPageFooterModel layoutForContainerSize:CGSizeMake(contentContainerWidth, contentContainerSize.height) contentSize:contentSize needsContentUpdate:&updateContents];
|
|
|
|
}
|
|
|
|
|
|
|
|
webPageSize = _webPageFooterModel.frame.size;
|
|
|
|
if ([_webPageFooterModel preferWebpageSize])
|
|
|
|
contentContainerWidth = webPageSize.width;
|
|
|
|
|
|
|
|
headerSize.width = MAX(headerSize.width, webPageSize.width);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
contentSize = [self contentSizeForContainerSize:CGSizeMake(contentContainerWidth, contentContainerSize.height) needsContentsUpdate:&updateContents hasDate:_webPageFooterModel == nil hasViews:_messageViews != nil];
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
CGFloat avatarOffset = 0.0f;
|
|
|
|
if (_hasAvatar)
|
|
|
|
avatarOffset = 38.0f;
|
|
|
|
|
|
|
|
CGFloat unsentOffset = 0.0f;
|
2015-10-01 18:19:52 +02:00
|
|
|
if (!_incomingAppearance && _deliveryState == TGMessageDeliveryStateFailed)
|
2014-07-10 16:11:09 +02:00
|
|
|
unsentOffset = 29.0f;
|
|
|
|
|
|
|
|
CGFloat backgroundWidth = MAX(60.0f, MAX(headerSize.width, contentSize.width) + 25.0f);
|
2015-10-01 18:19:52 +02:00
|
|
|
CGRect backgroundFrame = CGRectMake(_incomingAppearance ? (avatarOffset + layoutConstants->leftInset) : (containerSize.width - backgroundWidth - layoutConstants->rightInset - unsentOffset), topSpacing, backgroundWidth, MAX((_hasAvatar ? 44.0f : 30.0f), headerSize.height + contentSize.height + layoutConstants->textBubblePaddingTop + layoutConstants->textBubblePaddingBottom));
|
|
|
|
if (_incomingAppearance && _editing)
|
2014-07-10 16:11:09 +02:00
|
|
|
backgroundFrame.origin.x += 42.0f;
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
if (_webPageFooterModel != nil)
|
|
|
|
{
|
|
|
|
backgroundFrame.size.height += webPageSize.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
_contentModel.frame = CGRectMake(backgroundFrame.origin.x + (_incomingAppearance ? 14 : 8), topSpacing + 2.0f, MAX(32.0f, MAX(headerSize.width, contentSize.width) + 2 + (_incomingAppearance ? 0.0f : 5.0f)), MAX(headerSize.height + contentSize.height + 5, _hasAvatar ? 30.0f : 14.0f) + webPageSize.height);
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
_backgroundModel.frame = backgroundFrame;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_webPageFooterModel != nil)
|
|
|
|
{
|
|
|
|
[_webPageFooterModel updateSpecialViewsPositions:CGPointMake(_itemPosition.x + _webPageFooterModel.frame.origin.x, _itemPosition.y + headerSize.height + contentSize.height)];
|
|
|
|
|
|
|
|
[_webPageFooterModel layoutForContainerSize:contentContainerSize contentSize:contentSize needsContentUpdate:&updateContents];
|
|
|
|
|
|
|
|
_webPageFooterModel.frame = CGRectMake(0.0f, headerSize.height + contentSize.height, _webPageFooterModel.frame.size.width, _webPageFooterModel.frame.size.height);
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
|
|
|
|
if (_authorNameModel != nil)
|
|
|
|
{
|
|
|
|
CGRect authorModelFrame = _authorNameModel.frame;
|
|
|
|
authorModelFrame.origin.x = isRTL ? (_contentModel.frame.size.width - authorModelFrame.size.width - 1.0f) : 1.0f;
|
|
|
|
_authorNameModel.frame = authorModelFrame;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_forwardedHeaderModel != nil)
|
|
|
|
{
|
|
|
|
CGRect forwardedHeaderFrame = _forwardedHeaderModel.frame;
|
|
|
|
forwardedHeaderFrame.origin.x = isRTL ? (_contentModel.frame.size.width - forwardedHeaderFrame.size.width - 1.0f) : 1.0f;
|
|
|
|
_forwardedHeaderModel.frame = forwardedHeaderFrame;
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_replyHeaderModel != nil)
|
|
|
|
{
|
|
|
|
CGRect replyHeaderFrame = _replyHeaderModel.frame;
|
|
|
|
replyHeaderFrame.origin.x = isRTL ? (_contentModel.frame.size.width - replyHeaderFrame.size.width - 1.0f) : 1.0f;
|
|
|
|
_replyHeaderModel.frame = replyHeaderFrame;
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:11:09 +02:00
|
|
|
[self layoutContentForHeaderHeight:headerSize.height];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
_dateModel.frame = CGRectMake(_contentModel.frame.size.width - (_incomingAppearance ? (3 + TGRetinaPixel) : 20.0f) - _dateModel.frame.size.width, _contentModel.frame.size.height - 18.0f - (TGIsLocaleArabic() ? 1.0f : 0.0f), _dateModel.frame.size.width, _dateModel.frame.size.height);
|
|
|
|
|
|
|
|
if (_broadcastIconModel != nil)
|
|
|
|
{
|
|
|
|
_broadcastIconModel.frame = (CGRect){{_dateModel.frame.origin.x - 5.0f - _broadcastIconModel.frame.size.width, _dateModel.frame.origin.y + 3.0f + TGRetinaPixel}, _broadcastIconModel.frame.size};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_progressModel != nil) {
|
|
|
|
if (_incomingAppearance) {
|
|
|
|
_progressModel.frame = CGRectMake(CGRectGetMaxX(_backgroundModel.frame) - _dateModel.frame.size.width - 27.0f - layoutConstants->rightInset - unsentOffset + (TGIsPad() ? 12.0f : 0.0f), _contentModel.frame.origin.y + _contentModel.frame.size.height - 17 + 1.0f, 15, 15);
|
|
|
|
} else {
|
|
|
|
_progressModel.frame = CGRectMake(CGRectGetMaxX(_backgroundModel.frame) - 23.0f - layoutConstants->rightInset - unsentOffset + (TGIsPad() ? 12.0f : 0.0f), _contentModel.frame.origin.y + _contentModel.frame.size.height - 17 + 1.0f, 15, 15);
|
|
|
|
}
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
if (_messageViewsModel != nil) {
|
|
|
|
_messageViewsModel.frame = CGRectMake(CGRectGetMaxX(_backgroundModel.frame) - _dateModel.frame.size.width - 22.0f - (_incomingAppearance ? 0.0f : 14.0f), _contentModel.frame.origin.y + _contentModel.frame.size.height - 17 + 1.0f + TGRetinaPixel, 1.0f, 1.0f);
|
|
|
|
}
|
2014-07-10 16:11:09 +02:00
|
|
|
|
|
|
|
CGPoint stateOffset = _contentModel.frame.origin;
|
|
|
|
if (_checkFirstModel != nil)
|
|
|
|
_checkFirstModel.frame = CGRectMake((_checkFirstEmbeddedInContent ? 0.0f : stateOffset.x) + _contentModel.frame.size.width - 17, (_checkFirstEmbeddedInContent ? 0.0f : stateOffset.y) + _contentModel.frame.size.height - 14 + TGRetinaPixel, 12, 11);
|
|
|
|
|
|
|
|
if (_checkSecondModel != nil)
|
|
|
|
_checkSecondModel.frame = CGRectMake((_checkSecondEmbeddedInContent ? 0.0f : stateOffset.x) + _contentModel.frame.size.width - 13, (_checkSecondEmbeddedInContent ? 0.0f : stateOffset.y) + _contentModel.frame.size.height - 14 + TGRetinaPixel, 12, 11);
|
|
|
|
|
|
|
|
if (_unsentButtonModel != nil)
|
|
|
|
{
|
|
|
|
_unsentButtonModel.frame = CGRectMake(containerSize.width - _unsentButtonModel.frame.size.width - 9, backgroundFrame.size.height + topSpacing + bottomSpacing - _unsentButtonModel.frame.size.height - ((_collapseFlags & TGModernConversationItemCollapseBottom) ? 5 : 6), _unsentButtonModel.frame.size.width, _unsentButtonModel.frame.size.height);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.frame = CGRectMake(0, 0, containerSize.width, backgroundFrame.size.height + topSpacing + bottomSpacing);
|
|
|
|
|
|
|
|
bool tiledMode = _contentModel.frame.size.height > TGModernFlatteningViewModelTilingLimit;
|
|
|
|
[_contentModel setTiledMode:tiledMode];
|
|
|
|
self.needsRelativeBoundsUpdates = tiledMode;
|
|
|
|
|
|
|
|
if (updateContents)
|
|
|
|
{
|
|
|
|
[_contentModel setNeedsSubmodelContentsUpdate];
|
|
|
|
[_contentModel updateSubmodelContentsIfNeeded];
|
|
|
|
}
|
|
|
|
|
|
|
|
[super layoutForContainerSize:containerSize];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|