1
0
mirror of https://github.com/danog/Telegram.git synced 2025-01-22 05:52:06 +01:00
Telegram/Telegraph/TGStickerMessageViewModel.m
2015-10-01 19:19:52 +03:00

584 lines
24 KiB
Objective-C

#import "TGStickerMessageViewModel.h"
#import "TGMessageImageViewModel.h"
#import "TGMessage.h"
#import "TGUser.h"
#import "TGConversation.h"
#import "TGImageUtils.h"
#import "TGStringUtils.h"
#import "TGDateUtils.h"
#import "TGModernConversationItem.h"
#import "TGTelegraphConversationMessageAssetsSource.h"
#import "TGDoubleTapGestureRecognizer.h"
#import "TGModernViewContext.h"
#import "TGMessageImageView.h"
#import "TGModernImageViewModel.h"
#import "TGContentBubbleViewModel.h"
#import "TGReplyHeaderModel.h"
#import "TGModernFlatteningViewModel.h"
#import "TGModernImageViewModel.h"
#import "TGViewController.h"
@interface TGStickerMessageViewModel () <TGDoubleTapGestureRecognizerDelegate, UIGestureRecognizerDelegate>
{
bool _incoming;
bool _incomingAppearance;
bool _read;
TGMessageDeliveryState _deliveryState;
bool _hasAvatar;
CGSize _size;
float _progress;
bool _progressVisible;
TGDocumentMediaAttachment *_document;
TGMessageImageViewModel *_imageModel;
TGDoubleTapGestureRecognizer *_boundDoubleTapRecognizer;
UITapGestureRecognizer *_replyTapRecognizer;
TGModernImageViewModel *_unsentButtonModel;
UITapGestureRecognizer *_unsentButtonTapRecognizer;
UIImageView *_temporaryHighlightView;
TGModernFlatteningViewModel *_contentModel;
TGModernImageViewModel *_replyBackgroundModel;
TGReplyHeaderModel *_replyHeaderModel;
int32_t _replyMessageId;
TGMessageViewCountContentProperty *_messageViews;
}
@end
@implementation TGStickerMessageViewModel
- (CGSize)displaySizeForSize:(CGSize)size
{
CGSize maxSize = CGSizeMake(160, 170);
return TGFitSize(CGSizeMake(size.width / 2.0f, size.height / 2.0f), maxSize);
}
- (instancetype)initWithMessage:(TGMessage *)message document:(TGDocumentMediaAttachment *)document size:(CGSize)size authorPeer:(id)authorPeer context:(TGModernViewContext *)context replyHeader:(TGMessage *)replyHeader replyPeer:(id)replyPeer
{
self = [super initWithAuthorPeer:authorPeer context:context];
if (self != nil)
{
_mid = message.mid;
_incoming = !message.outgoing;
_read = !message.unread;
_deliveryState = message.deliveryState;
_hasAvatar = authorPeer != nil && [authorPeer isKindOfClass:[TGUser class]];
_messageViews = message.viewCount;
_needsEditingCheckButton = true;
_document = document;
_size = size;
_incomingAppearance = _incoming || [authorPeer isKindOfClass:[TGConversation class]];
_imageModel = [[TGMessageImageViewModel alloc] init];
_imageModel.expectExtendedEdges = true;
_imageModel.overlayBackgroundColorHint = UIColorRGBA(0x000000, 0.4f);
CGSize displaySize = [self displaySizeForSize:_size];
NSMutableString *imageUri = [[NSMutableString alloc] init];
[imageUri appendString:@"sticker://?"];
if (_document.documentId != 0)
[imageUri appendFormat:@"&documentId=%" PRId64, _document.documentId];
else
[imageUri appendFormat:@"&localDocumentId=%" PRId64, _document.localDocumentId];
[imageUri appendFormat:@"&accessHash=%" PRId64, _document.accessHash];
[imageUri appendFormat:@"&datacenterId=%d", (int)_document.datacenterId];
[imageUri appendFormat:@"&fileName=%@", [TGStringUtils stringByEscapingForURL:_document.fileName]];
[imageUri appendFormat:@"&size=%d", (int)_document.size];
[imageUri appendFormat:@"&width=%d&height=%d", (int)displaySize.width, (int)displaySize.height];
[imageUri appendFormat:@"&mime-type=%@", [TGStringUtils stringByEscapingForURL:_document.mimeType]];
[_imageModel setUri:imageUri];
_imageModel.frame = CGRectMake(0.0f, 0.0f, displaySize.width, displaySize.height);
_imageModel.skipDrawInContext = true;
[self addSubmodel:_imageModel];
[_imageModel setTimestampColor:[[TGTelegraphConversationMessageAssetsSource instance] systemMessageBackgroundColor]];
[_imageModel setTimestampString:[TGDateUtils stringForShortTime:(int)message.date] displayCheckmarks:!_incoming && _deliveryState != TGMessageDeliveryStateFailed checkmarkValue:(_incoming ? 0 : ((_deliveryState == TGMessageDeliveryStateDelivered ? 1 : 0) + (_read ? 1 : 0))) displayViews:_messageViews != nil viewsValue:_messageViews.viewCount animated:false];
[_imageModel setDisplayTimestampProgress:_deliveryState == TGMessageDeliveryStatePending];
[_imageModel setIsBroadcast:message.isBroadcast];
__weak TGStickerMessageViewModel *weakSelf = self;
_imageModel.completionBlock = ^(__unused TGImageView *imageView)
{
__strong TGStickerMessageViewModel *strongSelf = weakSelf;
if (strongSelf != nil)
{
if (strongSelf->_progressVisible)
[strongSelf updateProgressInternal:false progress:1.0f animated:true];
}
};
if (!_incoming)
{
if (_deliveryState == TGMessageDeliveryStatePending)
{
}
else if (_deliveryState == TGMessageDeliveryStateFailed)
{
[self addSubmodel:[self unsentButtonModel]];
}
else if (_deliveryState == TGMessageDeliveryStateDelivered)
{
}
}
if (replyHeader != nil)
{
_replyMessageId = replyHeader.mid;
_replyBackgroundModel = [[TGModernImageViewModel alloc] initWithImage:[[TGTelegraphConversationMessageAssetsSource instance] systemReplyBackground]];
_replyBackgroundModel.skipDrawInContext = true;
[self addSubmodel:_replyBackgroundModel];
_contentModel = [[TGModernFlatteningViewModel alloc] init];
[self addSubmodel:_contentModel];
_replyHeaderModel = [TGContentBubbleViewModel replyHeaderModelFromMessage:replyHeader peer:replyPeer incoming:_incomingAppearance system:true];
[_contentModel addSubmodel:_replyHeaderModel];
}
}
return self;
}
- (void)updateAssets
{
[super updateAssets];
[_imageModel setTimestampColor:[[TGTelegraphConversationMessageAssetsSource instance] systemMessageBackgroundColor]];
}
- (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;
}
- (void)updateProgressInternal:(bool)progressVisible progress:(float)progress animated:(bool)animated
{
bool progressWasVisible = _progressVisible;
float previousProgress = _progress;
_progress = progress;
_progressVisible = progressVisible;
[self updateImageOverlay:((progressWasVisible && !_progressVisible) || (_progressVisible && ABS(_progress - previousProgress) > FLT_EPSILON)) && animated];
}
- (void)updateImageOverlay:(bool)animated
{
if (_progressVisible)
{
[_imageModel setOverlayType:TGMessageImageViewOverlayProgressNoCancel animated:false];
[_imageModel setProgress:_progress animated:animated];
}
else
{
[_imageModel setOverlayType:TGMessageImageViewOverlayNone animated:animated];
}
}
- (void)updateMessage:(TGMessage *)message viewStorage:(TGModernViewStorage *)viewStorage sizeUpdated:(bool *)sizeUpdated
{
[super updateMessage:message viewStorage:viewStorage sizeUpdated:sizeUpdated];
_mid = message.mid;
if (_deliveryState != message.deliveryState || (!_incoming && _read != !message.unread) || (_messageViews != nil && _messageViews.viewCount != message.viewCount.viewCount))
{
_messageViews = message.viewCount;
_deliveryState = message.deliveryState;
_read = !message.unread;
[_imageModel setTimestampString:[TGDateUtils stringForShortTime:(int)message.date] displayCheckmarks:!_incoming && _deliveryState != TGMessageDeliveryStateFailed checkmarkValue:(_incoming ? 0 : ((_deliveryState == TGMessageDeliveryStateDelivered ? 1 : 0) + (_read ? 1 : 0))) displayViews:_messageViews != nil viewsValue:_messageViews.viewCount animated:true];
[_imageModel setDisplayTimestampProgress:_deliveryState == TGMessageDeliveryStatePending];
if (_deliveryState == TGMessageDeliveryStateDelivered)
{
if (_unsentButtonModel != nil)
{
[self removeSubmodel:_unsentButtonModel viewStorage:viewStorage];
_unsentButtonModel = nil;
}
}
else if (_deliveryState == TGMessageDeliveryStateFailed)
{
if (_unsentButtonModel == nil)
{
[self addSubmodel:[self unsentButtonModel]];
if ([_imageModel boundView] != nil)
[_unsentButtonModel bindViewToContainer:[_imageModel 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 ([_imageModel 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)];
}
}
else if (_deliveryState == TGMessageDeliveryStatePending)
{
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)
{
[[_imageModel 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 ([_imageModel 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)];
}
}
}
for (id attachment in message.mediaAttachments)
{
if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]])
{
_document = attachment;
}
}
CGSize displaySize = [self displaySizeForSize:_size];
NSMutableString *imageUri = [[NSMutableString alloc] init];
[imageUri appendString:@"sticker://?"];
if (_document.documentId != 0)
[imageUri appendFormat:@"&documentId=%" PRId64, _document.documentId];
else
[imageUri appendFormat:@"&localDocumentId=%" PRId64, _document.localDocumentId];
[imageUri appendFormat:@"&accessHash=%" PRId64, _document.accessHash];
[imageUri appendFormat:@"&datacenterId=%d", (int)_document.datacenterId];
[imageUri appendFormat:@"&fileName=%@", [TGStringUtils stringByEscapingForURL:_document.fileName]];
[imageUri appendFormat:@"&size=%d", (int)_document.size];
[imageUri appendFormat:@"&width=%d&height=%d", (int)displaySize.width, (int)displaySize.height];
[imageUri appendFormat:@"&mime-type=%@", [TGStringUtils stringByEscapingForURL:_document.mimeType]];
[_imageModel setUri:imageUri];
}
- (CGRect)effectiveContentFrame
{
return _imageModel.frame;
}
- (void)bindSpecialViewsToContainer:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage atItemPosition:(CGPoint)itemPosition
{
[super bindSpecialViewsToContainer:container viewStorage:viewStorage atItemPosition:itemPosition];
[_imageModel bindViewToContainer:container viewStorage:viewStorage];
[_imageModel boundView].frame = CGRectOffset([_imageModel boundView].frame, itemPosition.x, itemPosition.y);
_replyBackgroundModel.parentOffset = itemPosition;
[_replyBackgroundModel bindViewToContainer:container viewStorage:viewStorage];
_contentModel.parentOffset = itemPosition;
[_contentModel bindViewToContainer:container viewStorage:viewStorage];
[_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)];
}
- (void)bindViewToContainer:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage
{
[self updateEditingState:nil viewStorage:nil animationDelay:-1.0];
_replyBackgroundModel.parentOffset = CGPointZero;
_contentModel.parentOffset = CGPointZero;
[super bindViewToContainer:container viewStorage:viewStorage];
[_replyHeaderModel bindSpecialViewsToContainer:_contentModel.boundView viewStorage:viewStorage atItemPosition:CGPointMake(_replyHeaderModel.frame.origin.x, _replyHeaderModel.frame.origin.y)];
_boundDoubleTapRecognizer = [[TGDoubleTapGestureRecognizer alloc] initWithTarget:self action:@selector(messageDoubleTapGesture:)];
_boundDoubleTapRecognizer.delegate = self;
if (_replyHeaderModel != nil)
{
_replyTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(replyHeaderTapGesture:)];
[[_contentModel boundView] addGestureRecognizer:_replyTapRecognizer];
}
if (_unsentButtonModel != nil)
{
_unsentButtonTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(unsentButtonTapGesture:)];
[[_unsentButtonModel boundView] addGestureRecognizer:_unsentButtonTapRecognizer];
}
UIView *backgroundView = [_imageModel boundView];
[backgroundView addGestureRecognizer:_boundDoubleTapRecognizer];
}
- (void)unbindView:(TGModernViewStorage *)viewStorage
{
UIView *imageView = [_imageModel boundView];
[imageView removeGestureRecognizer:_boundDoubleTapRecognizer];
_boundDoubleTapRecognizer.delegate = nil;
_boundDoubleTapRecognizer = nil;
[[_contentModel boundView] removeGestureRecognizer:_replyTapRecognizer];
_replyTapRecognizer = nil;
if (_temporaryHighlightView != nil)
{
[_temporaryHighlightView removeFromSuperview];
_temporaryHighlightView = nil;
}
if (_unsentButtonModel != nil)
{
[[_unsentButtonModel boundView] removeGestureRecognizer:_unsentButtonTapRecognizer];
_unsentButtonTapRecognizer = nil;
}
[super unbindView:viewStorage];
}
- (void)unsentButtonTapGesture:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
[_context.companionHandle requestAction:@"showUnsentMessageMenu" options:@{@"mid": @(_mid)}];
}
}
- (void)replyHeaderTapGesture:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
[_context.companionHandle requestAction:@"navigateToMessage" options:@{@"mid": @(_replyMessageId), @"sourceMid": @(_mid)}];
}
}
- (void)messageDoubleTapGesture:(TGDoubleTapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
if (recognizer.longTapped || recognizer.doubleTapped)
[_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}];
}
}
- (bool)gestureRecognizerShouldHandleLongTap:(TGDoubleTapGestureRecognizer *)__unused recognizer
{
return true;
}
- (void)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer didBeginAtPoint:(CGPoint)__unused point
{
}
- (int)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer shouldFailTap:(CGPoint)__unused point
{
return 0;
}
- (void)doubleTapGestureRecognizerSingleTapped:(TGDoubleTapGestureRecognizer *)__unused recognizer
{
}
- (bool)gestureRecognizerShouldLetScrollViewStealTouches:(TGDoubleTapGestureRecognizer *)__unused recognizer
{
return true;
}
- (bool)gestureRecognizerShouldFailOnMove:(TGDoubleTapGestureRecognizer *)__unused recognizer
{
return true;
}
- (void)setTemporaryHighlighted:(bool)temporaryHighlighted viewStorage:(TGModernViewStorage *)__unused viewStorage
{
if (iosMajorVersion() >= 7)
{
TGImageView *imageView = ((TGMessageImageViewContainer *)_imageModel.boundView).imageView;
if (imageView.currentImage != nil)
{
if (temporaryHighlighted)
{
if (_temporaryHighlightView == nil)
{
UIImage *highlightImage = [imageView.currentImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
_temporaryHighlightView = [[UIImageView alloc] initWithImage:highlightImage];
_temporaryHighlightView.frame = [_imageModel boundView].frame;
_temporaryHighlightView.tintColor = UIColorRGBA(0x000000, 0.2f);
[[_imageModel boundView].superview addSubview:_temporaryHighlightView];
}
}
else if (_temporaryHighlightView != nil)
{
UIImageView *temporaryView = _temporaryHighlightView;
[UIView animateWithDuration:0.4 animations:^
{
temporaryView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
[temporaryView removeFromSuperview];
}];
_temporaryHighlightView = nil;
}
}
}
}
- (void)layoutForContainerSize:(CGSize)containerSize
{
bool isPost = _authorPeer != nil && [_authorPeer isKindOfClass:[TGConversation class]];
TGMessageViewModelLayoutConstants const *layoutConstants = TGGetMessageViewModelLayoutConstants();
CGFloat topSpacing = (_collapseFlags & TGModernConversationItemCollapseTop) ? layoutConstants->topInsetCollapsed : layoutConstants->topInset;
CGFloat bottomSpacing = (_collapseFlags & TGModernConversationItemCollapseBottom) ? layoutConstants->bottomInsetCollapsed : layoutConstants->bottomInset;
if (isPost) {
topSpacing = layoutConstants->topPostInset;
bottomSpacing = layoutConstants->bottomPostInset;
}
CGFloat avatarOffset = 0.0f;
if (_hasAvatar)
avatarOffset = 38.0f;
CGFloat unsentOffset = 0.0f;
if (!_incomingAppearance && _deliveryState == TGMessageDeliveryStateFailed)
unsentOffset = 29.0f;
CGRect imageFrame = CGRectMake(_incomingAppearance ? (avatarOffset + layoutConstants->leftImageInset) : (containerSize.width - _imageModel.frame.size.width - layoutConstants->rightImageInset - unsentOffset), topSpacing, _imageModel.frame.size.width, _imageModel.frame.size.height);
if (_incomingAppearance && _editing)
imageFrame.origin.x += 42.0f;
_imageModel.frame = imageFrame;
if (_contentModel != nil)
{
CGFloat availableWidth = containerSize.width - imageFrame.size.width - 40.0f - avatarOffset;
bool updateContent = false;
[_replyHeaderModel layoutForContainerSize:CGSizeMake(availableWidth, 0.0f) updateContent:&updateContent];
CGRect contentFrame = CGRectMake(0.0f, CGRectGetMaxY(imageFrame) - _replyHeaderModel.frame.size.height - 8.0f, _replyHeaderModel.frame.size.width + 14.0f, _replyHeaderModel.frame.size.height + 4.0f);
if (_incomingAppearance)
contentFrame.origin.x = containerSize.width - contentFrame.size.width - 7.0f;
else
contentFrame.origin.x = 9.0f + (_editing ? 42.0f : 0.0f);
_contentModel.frame = contentFrame;
_replyHeaderModel.frame = CGRectMake(5.0f, 0.0f, _replyHeaderModel.frame.size.width, _replyHeaderModel.frame.size.height);
_replyBackgroundModel.frame = CGRectMake(contentFrame.origin.x, contentFrame.origin.y + 3.0f, _replyHeaderModel.frame.size.width + 12.0f, _replyHeaderModel.frame.size.height - 1.0f);
if ((_incomingAppearance && _replyBackgroundModel.frame.origin.x < CGRectGetMaxX(imageFrame)) || (!_incomingAppearance && CGRectGetMaxX(_replyBackgroundModel.frame) > imageFrame.origin.x))
{
_contentModel.alpha = 0.0f;
_replyBackgroundModel.alpha = 0.0f;
}
else
{
_contentModel.alpha = 1.0f;
_replyBackgroundModel.alpha = 1.0f;
}
if (updateContent)
{
[_contentModel setNeedsSubmodelContentsUpdate];
[_contentModel updateSubmodelContentsIfNeeded];
}
}
if (_unsentButtonModel != nil)
{
_unsentButtonModel.frame = CGRectMake(containerSize.width - _unsentButtonModel.frame.size.width - 9, _imageModel.frame.size.height + topSpacing + bottomSpacing - _unsentButtonModel.frame.size.height - ((_collapseFlags & TGModernConversationItemCollapseBottom) ? 5 : 6), _unsentButtonModel.frame.size.width, _unsentButtonModel.frame.size.height);
}
CGRect frame = self.frame;
frame.size = CGSizeMake(containerSize.width, _imageModel.frame.size.height + topSpacing + bottomSpacing);
self.frame = frame;
[super layoutForContainerSize:containerSize];
}
@end