#import "TGTextMessageModernViewModel.h" #import "TGTelegraphConversationMessageAssetsSource.h" #import "TGImageUtils.h" #import "TGDateUtils.h" #import "TGStringUtils.h" #import "TGFont.h" #import "TGModernConversationItem.h" #import "TGModernView.h" #import "TGModernFlatteningViewModel.h" #import "TGModernImageViewModel.h" #import "TGModernTextViewModel.h" #import "TGModernLabelViewModel.h" #import "TGModernDateViewModel.h" #import "TGModernRemoteImageViewModel.h" #import "TGModernClockProgressViewModel.h" #import "TGTextMessageBackgroundViewModel.h" #import "TGReusableLabel.h" #import "TGMessage.h" #import "TGUser.h" #import "TGDoubleTapGestureRecognizer.h" @interface TGTextMessageModernViewModel () { TGModernTextViewModel *_textModel; bool _textIsRTL; NSArray *_currentLinkSelectionViews; } @end @implementation TGTextMessageModernViewModel static CTFontRef textFontForSize(CGFloat size) { static CTFontRef font = NULL; static int cachedSize = 0; if ((int)size != cachedSize || font == NULL) { font = TGCoreTextSystemFontOfSize(size); cachedSize = (int)size; } return font; } - (instancetype)initWithMessage:(TGMessage *)message author:(TGUser *)author context:(TGModernViewContext *)context { self = [super initWithMessage:message author:author context:context]; if (self != nil) { static TGTelegraphConversationMessageAssetsSource *assetsSource = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { assetsSource = [TGTelegraphConversationMessageAssetsSource instance]; }); _textModel = [[TGModernTextViewModel alloc] initWithText:message.text font:textFontForSize(TGGetMessageViewModelLayoutConstants()->textFontSize)]; _textModel.textCheckingResults = message.textCheckingResults; _textModel.textColor = [assetsSource messageTextColor]; _textModel.layoutFlags = TGReusableLabelLayoutMultiline | TGReusableLabelLayoutHighlightLinks | TGReusableLabelLayoutDateSpacing | (_incoming ? 0 : TGReusableLabelLayoutExtendedDateSpacing); [_contentModel addSubmodel:_textModel]; } return self; } - (void)refreshMetrics { [_textModel setFont:textFontForSize(TGGetMessageViewModelLayoutConstants()->textFontSize)]; } - (void)setIsUnsupported:(bool)isUnsupported { if (isUnsupported) { _textModel.text = TGLocalized(@"Conversation.UnsupportedMedia"); NSRange range = [_textModel.text rangeOfString:@"http://telegram.org/update"]; if (range.location != NSNotFound) { _textModel.textCheckingResults = @[[NSTextCheckingResult linkCheckingResultWithRange:range URL:[NSURL URLWithString:@"http://telegram.org/update"]]]; } } } - (void)unbindView:(TGModernViewStorage *)viewStorage { [self clearLinkSelection]; [super unbindView:viewStorage]; } - (void)messageDoubleTapGesture:(TGDoubleTapGestureRecognizer *)recognizer { if (recognizer.state != UIGestureRecognizerStateBegan) { [self clearLinkSelection]; if (recognizer.state == UIGestureRecognizerStateRecognized) { CGPoint point = [recognizer locationInView:[_contentModel boundView]]; NSString *linkCandidate = [_textModel linkAtPoint:CGPointMake(point.x - _textModel.frame.origin.x, point.y - _textModel.frame.origin.y) regionData:NULL]; if (recognizer.longTapped) { if (linkCandidate != nil) [_context.companionHandle requestAction:@"openLinkWithOptionsRequested" options:@{@"url": linkCandidate}]; else [_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}]; } else if (recognizer.doubleTapped) [_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}]; else if (linkCandidate != nil) [_context.companionHandle requestAction:@"openLinkRequested" options:@{@"url": linkCandidate}]; else if (_forwardedHeaderModel && CGRectContainsPoint(_forwardedHeaderModel.frame, point)) [_context.companionHandle requestAction:@"userAvatarTapped" options:@{@"uid": @(_forwardedUid)}]; } } } - (void)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer didBeginAtPoint:(CGPoint)point { [self updateLinkSelection:point]; } - (void)gestureRecognizerDidFail:(TGDoubleTapGestureRecognizer *)__unused recognizer { [self clearLinkSelection]; } - (bool)gestureRecognizerShouldHandleLongTap:(TGDoubleTapGestureRecognizer *)__unused recognizer { return true; } - (int)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer shouldFailTap:(CGPoint)point { if ([_textModel linkAtPoint:CGPointMake(point.x - _textModel.frame.origin.x, point.y - _textModel.frame.origin.y) regionData:NULL] != nil || (_forwardedHeaderModel && CGRectContainsPoint(_forwardedHeaderModel.frame, point))) return 3; return false; } - (void)clearLinkSelection { for (UIView *linkView in _currentLinkSelectionViews) { [linkView removeFromSuperview]; } _currentLinkSelectionViews = nil; } - (void)updateLinkSelection:(CGPoint)point { if ([_contentModel boundView] != nil) { [self clearLinkSelection]; NSArray *regionData = nil; NSString *link = [_textModel linkAtPoint:CGPointMake(point.x - _textModel.frame.origin.x, point.y - _textModel.frame.origin.y) regionData:®ionData]; if (link != nil) { CGRect topRegion = regionData.count > 0 ? [regionData[0] CGRectValue] : CGRectZero; CGRect middleRegion = regionData.count > 1 ? [regionData[1] CGRectValue] : CGRectZero; CGRect bottomRegion = regionData.count > 2 ? [regionData[2] CGRectValue] : CGRectZero; UIImageView *topView = nil; UIImageView *middleView = nil; UIImageView *bottomView = nil; UIImageView *topCornerLeft = nil; UIImageView *topCornerRight = nil; UIImageView *bottomCornerLeft = nil; UIImageView *bottomCornerRight = nil; NSMutableArray *linkHighlightedViews = [[NSMutableArray alloc] init]; topView = [[UIImageView alloc] init]; middleView = [[UIImageView alloc] init]; bottomView = [[UIImageView alloc] init]; topCornerLeft = [[UIImageView alloc] init]; topCornerRight = [[UIImageView alloc] init]; bottomCornerLeft = [[UIImageView alloc] init]; bottomCornerRight = [[UIImageView alloc] init]; if (topRegion.size.height != 0) { topView.hidden = false; topView.frame = topRegion; if (middleRegion.size.height == 0 && bottomRegion.size.height == 0) topView.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkFull]; else topView.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkFull]; } else { topView.hidden = true; topView.frame = CGRectZero; } if (middleRegion.size.height != 0) { middleView.hidden = false; middleView.frame = middleRegion; if (bottomRegion.size.height == 0) middleView.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkFull]; else middleView.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkFull]; } else { middleView.hidden = true; middleView.frame = CGRectZero; } if (bottomRegion.size.height != 0) { bottomView.hidden = false; bottomView.frame = bottomRegion; bottomView.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkFull]; } else { bottomView.hidden = true; bottomView.frame = CGRectZero; } topCornerLeft.hidden = true; topCornerRight.hidden = true; bottomCornerLeft.hidden = true; bottomCornerRight.hidden = true; if (topRegion.size.height != 0 && middleRegion.size.height != 0) { if (topRegion.origin.x == middleRegion.origin.x) { topCornerLeft.hidden = false; topCornerLeft.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerLR]; topCornerLeft.frame = CGRectMake(topRegion.origin.x, topRegion.origin.y + topRegion.size.height - 3.5f, 4, 7); } else if (topRegion.origin.x < middleRegion.origin.x + middleRegion.size.width - 3.5f) { topCornerLeft.hidden = false; topCornerLeft.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerBT]; topCornerLeft.frame = CGRectMake(topRegion.origin.x - 3.5f, topRegion.origin.y + topRegion.size.height - 4, 7, 4); } if (topRegion.origin.x + topRegion.size.width == middleRegion.origin.x + middleRegion.size.width) { topCornerRight.hidden = false; topCornerRight.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerRL]; topCornerRight.frame = CGRectMake(topRegion.origin.x + topRegion.size.width - 4, topRegion.origin.y + topRegion.size.height - 3.5f, 4, 7); } else if (topRegion.origin.x + topRegion.size.width < middleRegion.origin.x + middleRegion.size.width - 3.5f) { topCornerRight.hidden = false; topCornerRight.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerBT]; topCornerRight.frame = CGRectMake(topRegion.origin.x + topRegion.size.width - 3.5f, topRegion.origin.y + topRegion.size.height - 4, 7, 4); } else if (bottomRegion.size.height == 0 && topRegion.origin.x < middleRegion.origin.x + middleRegion.size.width - 3.5f && topRegion.origin.x + topRegion.size.width > middleRegion.origin.x + middleRegion.size.width + 3.5f) { topCornerRight.hidden = false; topCornerRight.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerTB]; topCornerRight.frame = CGRectMake(middleRegion.origin.x + middleRegion.size.width - 3.5f, middleRegion.origin.y, 7, 4); } } if (middleRegion.size.height != 0 && bottomRegion.size.height != 0) { if (middleRegion.origin.x == bottomRegion.origin.x) { bottomCornerLeft.hidden = false; bottomCornerLeft.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerLR]; bottomCornerLeft.frame = CGRectMake(middleRegion.origin.x, middleRegion.origin.y + middleRegion.size.height - 3.5f, 4, 7); } if (bottomRegion.origin.x + bottomRegion.size.width < middleRegion.origin.x + middleRegion.size.width - 3.5f) { bottomCornerRight.hidden = false; bottomCornerRight.image = [[TGTelegraphConversationMessageAssetsSource instance] messageLinkCornerTB]; bottomCornerRight.frame = CGRectMake(bottomRegion.origin.x + bottomRegion.size.width - 3.5f, bottomRegion.origin.y, 7, 4); } } if (!topView.hidden) [linkHighlightedViews addObject:topView]; if (!middleView.hidden) [linkHighlightedViews addObject:middleView]; if (!bottomView.hidden) [linkHighlightedViews addObject:bottomView]; if (!topCornerLeft.hidden) [linkHighlightedViews addObject:topCornerLeft]; if (!topCornerRight.hidden) [linkHighlightedViews addObject:topCornerRight]; if (!bottomCornerLeft.hidden) [linkHighlightedViews addObject:bottomCornerLeft]; if (!bottomCornerRight.hidden) [linkHighlightedViews addObject:bottomCornerRight]; for (UIView *partView in linkHighlightedViews) { partView.frame = CGRectOffset(partView.frame, _textModel.frame.origin.x, _textModel.frame.origin.y + 1); [[_contentModel boundView] addSubview:partView]; } _currentLinkSelectionViews = linkHighlightedViews; } } } - (void)layoutContentForHeaderHeight:(CGFloat)headerHeight { CGRect textFrame = _textModel.frame; textFrame.origin = CGPointMake(1, headerHeight + TGGetMessageViewModelLayoutConstants()->textBubbleTextOffsetTop); _textModel.frame = textFrame; } - (CGSize)contentSizeForContainerSize:(CGSize)containerSize needsContentsUpdate:(bool *)needsContentsUpdate { bool updateContents = [_textModel layoutNeedsUpdatingForContainerSize:containerSize]; if (updateContents) [_textModel layoutForContainerSize:containerSize]; if (needsContentsUpdate != NULL) *needsContentsUpdate = updateContents; return _textModel.frame.size; } @end