#import "TGModernConversationController.h" #import "FreedomUIKit.h" #import "TGPeerIdAdapter.h" #import "TGUpdateStateRequestBuilder.h" #import "TGModernConversationCompanion.h" #import "TGImagePickerController.h" #import "TGModernConversationCollectionView.h" #import "TGModernConversationViewLayout.h" #import "TGModernConversationItem.h" #import "TGModernFlatteningViewModel.h" #import "TGModernTemporaryView.h" #import "TGImageUtils.h" #import "TGPhoneUtils.h" #import "TGStringUtils.h" #import "TGFileUtils.h" #import "TGFont.h" #import "TGHacks.h" #import "TGObserverProxy.h" #import "TGActionSheet.h" #import "HPGrowingTextView.h" #import "HPTextViewInternal.h" #import "TGMessageModernConversationItem.h" #import "TGMessage.h" #import "TGAppDelegate.h" #import "TGApplication.h" #import "TGInterfaceManager.h" #import "TGModernConversationTitleView.h" #import "TGModernConversationAvatarButton.h" #import "TGModernConversationInputTextPanel.h" #import "TGModernConversationEditingPanel.h" #import "TGModernConversationTitlePanel.h" #import "TGModernConversationEmptyListPlaceholderView.h" #import "TGOverlayControllerWindow.h" #import "TGModernGalleryController.h" #import "TGGenericPeerMediaGalleryModel.h" #import "TGGroupAvatarGalleryModel.h" #import "TGGroupAvatarGalleryItem.h" #import "TGSecretPeerMediaGalleryModel.h" #import "TGSecretInfiniteLifetimePeerMediaGalleryModel.h" #import "TGGenericPeerGalleryItem.h" #import "TGModernGalleryVideoItemView.h" #import "TGDropboxHelper.h" #import "TGICloudItem.h" #import "TGGoogleDriveController.h" #import "TGLocationViewController.h" #import "TGLocationPickerController.h" #import "TGWebSearchController.h" #import "TGLegacyCameraController.h" #import "TGDocumentController.h" #import "TGForwardContactPickerController.h" #import "TGAudioRecorder.h" #import "TGModernConversationAudioPlayer.h" #import "PGCamera.h" #import "TGCameraPreviewView.h" #import "TGCameraController.h" #import "TGAccessChecker.h" #import "UIDevice+PlatformInfo.h" #import "TGMediaItem.h" #import "TGDatabase.h" #import "TGTelegraph.h" #import "TGGenericModernConversationCompanion.h" #import "TGModernConversationEmptyListPlaceholderView.h" #import "TGRemoteImageView.h" #import "TGMenuView.h" #import "TGAlertView.h" #import "TGWallpaperManager.h" #import #import #import #import "TGVideoConverter.h" #import "TGGiphySearchResultItem.h" #import "TGBingSearchResultItem.h" #import "TGWebSearchInternalImageResult.h" #import "TGWebSearchInternalGifResult.h" #import "TGExternalGifSearchResultItem.h" #import "TGInternalGifSearchResultItem.h" #import "TGAttachmentSheetWindow.h" #import "TGAttachmentSheetButtonItemView.h" #import "ATQueue.h" #import "TGProgressWindow.h" #import "TGModernConversationControllerDynamicTypeSignals.h" #import "TGMessageViewModel.h" #import "TGModenConcersationReplyAssociatedPanel.h" #import "TGStickerAssociatedInputPanel.h" #import "TGModernConversationMentionsAssociatedPanel.h" #import "TGModernConversationHashtagsAssociatedPanel.h" #import "TGModernConversationForwardInputPanel.h" #import "TGModernConversationWebPreviewInputPanel.h" #import "TGExternalGalleryModel.h" #import "TGStickersSignals.h" #import "TGCommandKeyboardView.h" #import "TGModernConversationCommandsAssociatedPanel.h" #import "TGEmbedPreviewController.h" #import "TGSearchBar.h" #import "TGGlobalMessageSearchSignals.h" #import "TGModernConversationSearchInputPanel.h" #import "TGAttachmentSheetEmbedItemView.h" #import "TGModernDateHeaderView.h" #import "TGModernConversationControllerView.h" #import "TGMenuSheetController.h" #import "TGAttachmentCameraView.h" #import "TGAttachmentCarouselItemView.h" #import "TGAttachmentFileTipView.h" #import "TGMediaAssetsController.h" #import "TGTelegramNetworking.h" #import #import "TGSendMessageSignals.h" #import "TGShareSheetView.h" #import "TGShareSheetWindow.h" #import "TGShareSheetSharePeersItemView.h" #import "TGPreparedForwardedMessage.h" #import "TGSendMessageSignals.h" #import "TGRecentGifsSignal.h" #import "TGBotContextResults.h" #import "TGModernConversationGenericContextResultsAssociatedPanel.h" #import "TGModernConversationMediaContextResultsAssociatedPanel.h" #import "TGBotContextResultAttachment.h" #import "TGRecentContextBotsSignal.h" #import "TGStickerKeyboardView.h" #import "TGExternalImageSearchResult.h" #import "TGExternalGifSearchResult.h" #import #import "TGProgressAlert.h" #import "TGBotContextDocumentResult.h" #import "TGBotContextImageResult.h" #import "TGBotContextExternalResult.h" #import "TGBotContextResultSendMessageAuto.h" #import "TGBotContextResultSendMessageText.h" #import "TGRaiseToListenActivator.h" #import "TGModernConversationAudioPreviewInputPanel.h" #import "TGKeyCommandController.h" #import "TGPopoverController.h" #import "TGMessageSearchSignals.h" #import "TGChannelConversationCompanion.h" #if TARGET_IPHONE_SIMULATOR NSInteger TGModernConversationControllerUnloadHistoryLimit = 500; NSInteger TGModernConversationControllerUnloadHistoryThreshold = 200; #else NSInteger TGModernConversationControllerUnloadHistoryLimit = 500; NSInteger TGModernConversationControllerUnloadHistoryThreshold = 200; #endif #define TGModernConversationControllerLogCellOperations false typedef enum { TGModernConversationActivityChangeAuto = 0, TGModernConversationActivityChangeActive = 1, TGModernConversationActivityChangeInactive = 2 } TGModernConversationActivityChange; typedef enum { TGModernConversationPanelAnimationNone = 0, TGModernConversationPanelAnimationSlide = 1, TGModernConversationPanelAnimationFade = 2, TGModernConversationPanelAnimationSlideFar = 3 } TGModernConversationPanelAnimation; @interface TGModernConversationController () { bool _alreadyHadWillAppear; bool _alreadyHadDidAppear; NSTimeInterval _willAppearTimestamp; bool _receivedWillDisappear; bool _didDisappearBeforeAppearing; NSString *_initialInputText; NSRange _initialSelectRange; NSArray *_initialForwardMessages; TGMessageEditingContext *_initialMessageEdigingContext; bool _shouldHaveTitlePanelLoaded; bool _editingMode; NSMutableArray *_items; NSMutableSet *_collectionRegisteredIdentifiers; TGModernConversationControllerView *_view; TGModernConversationCollectionView *_collectionView; TGModernConversationViewLayout *_collectionLayout; UIScrollView *_collectionViewScrollToTopProxy; TGModernViewStorage *_viewStorage; NSMutableArray *_itemsBoundToTemporaryContainer; bool _disableItemBinding; CGImageRef _snapshotImage; TGModernTemporaryView *_snapshotBackgroundView; UIView *_snapshotImageView; UIImageView *_backgroundView; TGModernConversationInputTextPanel *_inputTextPanel; TGModernConversationInputPanel *_currentInputPanel; TGModernConversationInputPanel *_customInputPanel; UIView *_titlePanelWrappingView; TGModernConversationTitlePanel *_primaryTitlePanel; TGModernConversationTitlePanel *_secondaryTitlePanel; TGModernConversationTitlePanel *_currentTitlePanel; TGModernConversationEmptyListPlaceholderView *_emptyListPlaceholder; bool _isRotating; CGFloat _keyboardHeight; CGFloat _halfTransitionKeyboardHeight; TGObserverProxy *_keyboardWillHideProxy; TGObserverProxy *_keyboardWillChangeFrameProxy; TGObserverProxy *_keyboardDidChangeFrameProxy; TGObserverProxy *_applicationWillResignActiveProxy; TGObserverProxy *_applicationDidEnterBackgroundProxy; TGObserverProxy *_applicationDidBecomeActiveProxy; TGObserverProxy *_screenshotProxy; TGObserverProxy *_dropboxProxy; CGPoint _collectionPanTouchContentOffset; bool _collectionPanStartedAtBottom; TGModernConversationTitleView *_titleView; TGModernConversationAvatarButton *_avatarButton; UIBarButtonItem *_avatarButtonItem; UIBarButtonItem *_infoButtonItem; TGMenuContainerView *_menuContainerView; TGMenuContainerView *_tooltipContainerView; UIButton *_unseenMessagesButton; bool _disableScrollProcessing; bool _enableAboveHistoryRequests; bool _enableBelowHistoryRequests; bool _enableUnloadHistoryRequests; bool _canReadHistory; TGAudioRecorder *_currentAudioRecorder; bool _currentAudioRecorderIsTouchInitiated; NSUserActivity *_currentActivity; TGShareSheetWindow *_shareSheetWindow; UIDocumentInteractionController *_interactionController; TGICloudItemRequest *_currentICloudItemRequest; SDisposableSet *_disposable; int32_t _temporaryHighlightMessageIdUponDisplay; bool _hasUnseenMessagesBelow; int32_t _scrollBackMessageId; bool _canCheckScrollBackMessageId; NSString *_currentLinkParseLink; SMetaDisposable *_currentLinkParseDisposable; bool _disableLinkPreviewsForMessage; TGBotReplyMarkup *_replyMarkup; bool _hasBots; bool _canBroadcast; bool _isBroadcasting; bool _isAlwaysBroadcasting; bool _inputDisabled; bool _isChannel; UIView *_conversationHeader; NSNumber *_scrollingToBottom; TGSearchBar *_searchBar; TGModernConversationSearchInputPanel *_searchPanel; NSString *_query; SMetaDisposable *_searchDisposable; NSArray *_searchResultsIds; NSUInteger _searchResultsOffset; int32_t _messageIdForVisibleHoleDirection; bool _loadingMessages; UIActivityIndicatorView *_loadingMessagesIndicator; SMetaDisposable *_mentionTextResultsDisposable; SMetaDisposable *_recentGifsDisposable; NSTimeInterval _mentionTextResultsRequestTimestamp; SMetaDisposable *_processMediaDisposable; TGProgressAlert *_progressAlert; TGAttachmentSheetWindow *_attachmentSheetWindow; SMetaDisposable *_inputPlaceholderForTextDisposable; __weak TGMenuSheetController *_menuController; TGRaiseToListenActivator *_raiseToListenActivator; TGTimer *_raiseToListenRecordAfterPlaybackTimer; id _playlistFinishedDisposable; SMetaDisposable *_saveEditedMessageDisposable; SMetaDisposable *_editingContextDisposable; SVariable *_currentEditingMessageContext; } @end @implementation TGModernConversationController - (instancetype)init { self = [super init]; if (self != nil) { _actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true]; self.automaticallyManageScrollViewInsets = false; self.adjustControllerInsetWhenStartingRotation = true; _items = [[NSMutableArray alloc] init]; _keyboardWillHideProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(keyboardWillHide:) name:@"UIDeviceOrientationDidChangeNotification"]; _keyboardWillChangeFrameProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification]; _keyboardDidChangeFrameProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification]; _applicationWillResignActiveProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification]; _applicationDidEnterBackgroundProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification]; _applicationDidBecomeActiveProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification]; if (iosMajorVersion() >= 7) { _screenshotProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(serviceNotificationReceived:) name:UIApplicationUserDidTakeScreenshotNotification]; } _titleView = [[TGModernConversationTitleView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)]; _titleView.delegate = self; [self setTitleView:_titleView]; _avatarButton = [[TGModernConversationAvatarButton alloc] initWithFrame:CGRectMake(0, 0, 1, 1)]; [_avatarButton addTarget:self action:@selector(avatarPressed) forControlEvents:UIControlEventTouchUpInside]; _avatarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:_avatarButton]; [self setRightBarButtonItem:[self defaultRightBarButtonItem]]; [self setLeftBarButtonItem:[self defaultLeftBarButtonItem]]; _canReadHistory = true; _enableUnloadHistoryRequests = true; self.dismissPresentedControllerWhenRemovedFromNavigationStack = true; _didDisappearBeforeAppearing = false; _disposable = [[SDisposableSet alloc] init]; if (iosMajorVersion() >= 7) { __weak TGModernConversationController *weakSelf = self; [_disposable add:[[TGModernConversationControllerDynamicTypeSignals dynamicTypeBaseFontPointSize] startWithNext:^(NSNumber *pointSize) { TGUpdateMessageViewModelLayoutConstants([pointSize floatValue]); __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) [strongSelf refreshMetrics]; } error:nil completed:nil]]; } _processMediaDisposable = [[SMetaDisposable alloc] init]; _inputPlaceholderForTextDisposable = [[SMetaDisposable alloc] init]; } return self; } - (void)dealloc { [_disposable dispose]; [_actionHandle reset]; _collectionView.delegate = nil; _collectionView.dataSource = nil; if (_snapshotImage != nil) { CGImageRelease(_snapshotImage); _snapshotImage = nil; } [_companion unbindController]; if (_shareSheetWindow != nil) _shareSheetWindow.rootViewController = nil; [_mentionTextResultsDisposable dispose]; [_recentGifsDisposable dispose]; [_inputPlaceholderForTextDisposable dispose]; [_processMediaDisposable dispose]; [_playlistFinishedDisposable dispose]; [_saveEditedMessageDisposable dispose]; [_editingContextDisposable dispose]; } - (NSInteger)_indexForCollectionView { return 1; } - (void)_resetCollectionView { [self _resetCollectionView:false]; } - (void)_resetCollectionView:(bool)resetPositioning { if (_collectionView != nil) { _collectionView.delegate = nil; _collectionView.dataSource = nil; [_collectionView removeFromSuperview]; UICollectionView *collectionView = _collectionView; dispatch_async(dispatch_get_main_queue(), ^ { TGLog(@"***** replacing collection view %@", collectionView); }); } if (_collectionViewScrollToTopProxy != nil) { _collectionViewScrollToTopProxy.delegate = nil; [_collectionViewScrollToTopProxy removeFromSuperview]; _collectionViewScrollToTopProxy = nil; } _collectionRegisteredIdentifiers = [[NSMutableSet alloc] init]; CGSize collectionViewSize = _view.bounds.size; _collectionLayout = [[TGModernConversationViewLayout alloc] init]; _collectionView = [[TGModernConversationCollectionView alloc] initWithFrame:CGRectMake(0, -210.0f, collectionViewSize.width, collectionViewSize.height + 210.0f) collectionViewLayout:_collectionLayout]; _collectionView.hidden = _loadingMessages; [_companion _setControllerWidthForItemCalculation:_collectionView.frame.size.width]; _collectionView.headerView = _conversationHeader; _collectionView.transform = CGAffineTransformMakeRotation((CGFloat)M_PI); _collectionView.backgroundColor = nil; _collectionView.opaque = false; _collectionView.scrollsToTop = false; _collectionView.dataSource = self; _collectionView.delegate = self; _collectionView.delaysContentTouches = false; if (iosMajorVersion() >= 7) { UIPanGestureRecognizer *collectionPanRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(collectionViewPan:)]; collectionPanRecognizer.delegate = self; [_collectionView addGestureRecognizer:collectionPanRecognizer]; } _collectionView.unreadMessageRange = [_companion unreadMessageRange]; _collectionView.alwaysBounceVertical = true; [_collectionView registerClass:[TGModernCollectionCell class] forCellWithReuseIdentifier:@"_empty"]; UIEdgeInsets contentInset = _collectionView.contentInset; contentInset.bottom = 210.0f + [_collectionView implicitTopInset]; contentInset.top = _keyboardHeight + _currentInputPanel.frame.size.height; _collectionView.contentInset = contentInset; [self _adjustCollectionInset]; [_emptyListPlaceholder adjustLayoutForSize:collectionViewSize contentInsets:UIEdgeInsetsMake(_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset], 0.0f, _collectionView.contentInset.top, 0.0f) duration:0.0f curve:0]; [_view insertSubview:_collectionView atIndex:[self _indexForCollectionView]]; _collectionViewScrollToTopProxy = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, _view.frame.size.width, 8)]; _collectionViewScrollToTopProxy.autoresizingMask = UIViewAutoresizingFlexibleWidth; _collectionViewScrollToTopProxy.delegate = self; _collectionViewScrollToTopProxy.scrollsToTop = true; _collectionViewScrollToTopProxy.contentSize = CGSizeMake(1, 16); _collectionViewScrollToTopProxy.contentOffset = CGPointMake(0, 8); [_view insertSubview:_collectionViewScrollToTopProxy belowSubview:_collectionView]; if (resetPositioning) { int32_t messageId = [_companion initialPositioningMessageId]; TGInitialScrollPosition scrollPosition = [_companion initialPositioningScrollPosition]; if (messageId != 0) { _collectionView.contentOffset = CGPointMake(0.0f, [self contentOffsetForMessageId:messageId scrollPosition:scrollPosition initial:true additionalOffset:_companion.previewMode ? self.controllerInset.top : 0.0f]); } } [_collectionView layoutSubviews]; [self _updateVisibleItemIndices:nil]; } - (CGFloat)contentOffsetForMessageId:(int32_t)messageId scrollPosition:(TGInitialScrollPosition)scrollPosition initial:(bool)initial additionalOffset:(CGFloat)additionalOffset { if (![_collectionLayout hasLayoutAttributes]) [_collectionLayout prepareLayout]; CGFloat contentOffsetY = _collectionView.contentOffset.y; int index = -1; for (TGMessageModernConversationItem *messageItem in _items) { index++; if (messageItem->_message.mid == messageId) { if (index == 0 && initial) { return -_collectionView.contentInset.top; } UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; if (attributes != nil) { switch (scrollPosition) { case TGInitialScrollPositionTop: contentOffsetY = CGRectGetMaxY(attributes.frame) + _collectionView.contentInset.bottom - [_collectionView implicitTopInset] - _collectionView.frame.size.height + [_companion initialPositioningOverflowForScrollPosition:scrollPosition]; break; case TGInitialScrollPositionCenter: { CGFloat visibleHeight = _collectionView.frame.size.height - _collectionView.contentInset.top - _collectionView.contentInset.bottom + [_collectionView implicitTopInset]; contentOffsetY = CGFloor(CGRectGetMidY(attributes.frame) - visibleHeight / 2.0f - _collectionView.contentInset.top); break; } case TGInitialScrollPositionBottom: contentOffsetY = attributes.frame.origin.y - _collectionView.contentInset.top - [_companion initialPositioningOverflowForScrollPosition:scrollPosition]; break; default: break; } } break; } } contentOffsetY += additionalOffset; if (contentOffsetY > _collectionLayout.collectionViewContentSize.height + _collectionView.contentInset.bottom - _collectionView.frame.size.height) { contentOffsetY = _collectionLayout.collectionViewContentSize.height + _collectionView.contentInset.bottom - _collectionView.frame.size.height; } if (contentOffsetY < -_collectionView.contentInset.top) { contentOffsetY = -_collectionView.contentInset.top; } return contentOffsetY; } - (void)setInitialSnapshot:(CGImageRef)image backgroundView:(TGModernTemporaryView *)backgroundView viewStorage:(TGModernViewStorage *)viewStorage topEdge:(CGFloat)topEdge { if (_viewStorage == nil && viewStorage != nil) _viewStorage = viewStorage; if (_snapshotImage != NULL) { CGImageRelease(_snapshotImage); _snapshotImage = nil; } if (_snapshotBackgroundView != nil) { [_snapshotBackgroundView unbindItems]; [_snapshotBackgroundView removeFromSuperview]; _snapshotBackgroundView = nil; } if (image != NULL) _snapshotImage = CGImageRetain(image); if (self.isViewLoaded) { if (_snapshotImage != NULL) { if (_snapshotImageView == nil) { _snapshotImageView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGImageGetWidth(_snapshotImage) * (TGIsRetina() ? 0.5f : 1.0f), CGImageGetHeight(_snapshotImage) * (TGIsRetina() ? 0.5f : 1.0f))]; _snapshotImageView.userInteractionEnabled = false; _snapshotImageView.transform = CGAffineTransformMakeRotation((CGFloat)M_PI); _snapshotImageView.hidden = _loadingMessages; [_view insertSubview:_snapshotImageView atIndex:[self _indexForCollectionView]]; } _snapshotBackgroundView = backgroundView; _snapshotBackgroundView.hidden = _loadingMessages; if (_snapshotBackgroundView != nil) { [_view insertSubview:_snapshotBackgroundView belowSubview:_snapshotImageView]; } _snapshotImageView.layer.contents = (__bridge id)_snapshotImage; if (_conversationHeader != nil) { [_view addSubview:_conversationHeader]; CGRect headerFrame = _conversationHeader.frame; headerFrame.origin.x = CGFloor((_view.frame.size.width - headerFrame.size.width) / 2.0f); headerFrame.origin.y = _view.frame.size.height - _conversationHeader.frame.size.height - topEdge - 4.0f; CGFloat visibleHeight = _view.frame.size.height - self.controllerInset.top - self.controllerInset.bottom - 44.0f; headerFrame.origin.y = MIN(headerFrame.origin.y, self.controllerInset.top + CGFloor((visibleHeight - headerFrame.size.height) / 2.0f)); _conversationHeader.frame = headerFrame; } } else { if (_snapshotImageView != nil) { [_snapshotImageView removeFromSuperview]; _snapshotImageView = nil; } if (_collectionView == nil) { [self _resetCollectionView:true]; } [self _updateVisibleItemIndices:nil]; } } } - (UIBarButtonItem *)defaultLeftBarButtonItem { if (_inputTextPanel.messageEditingContext != nil) { return [[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(messageEditingCancelPressed)]; } if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) return nil; if (self.isFirstInStack) { return [[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Close") style:UIBarButtonItemStylePlain target:self action:@selector(closeButtonPressed)]; } return nil; } - (void)setIsFirstInStack:(bool)isFirstInStack { if (isFirstInStack != self.isFirstInStack) { [super setIsFirstInStack:isFirstInStack]; [self setLeftBarButtonItem:[self defaultLeftBarButtonItem] animated:false]; } } - (UIBarButtonItem *)defaultRightBarButtonItem { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) return _avatarButtonItem; else { if (_infoButtonItem == nil && _companion != nil) { _infoButtonItem = [[UIBarButtonItem alloc] initWithTitle:[_companion _controllerInfoButtonText] style:UIBarButtonItemStylePlain target:self action:@selector(infoButtonPressed)]; } return _infoButtonItem; } } - (void)loadView { [super loadView]; [self check3DTouch]; _view = [[TGModernConversationControllerView alloc] initWithFrame:self.view.bounds]; _view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; __weak TGModernConversationController *weakSelf = self; _view.layoutForSize = ^(CGSize size) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_keyboardHeight < FLT_EPSILON) { [strongSelf _performSizeChangesWithDuration:strongSelf->_isRotating ? 0.3 : 0.0 size:size]; } } }; [self.view addSubview:_view]; _view.clipsToBounds = true; _view.backgroundColor = [UIColor whiteColor]; _backgroundView = [[UIImageView alloc] initWithFrame:_view.bounds]; UIImage *wallpaperImage = [[TGWallpaperManager instance] currentWallpaperImage]; _backgroundView.image = wallpaperImage; _backgroundView.clipsToBounds = true; _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _backgroundView.contentMode = UIViewContentModeScaleAspectFill; [_view addSubview:_backgroundView]; _inputTextPanel = [[TGModernConversationInputTextPanel alloc] initWithFrame:CGRectMake(0, _view.frame.size.height - 45, _view.frame.size.width, 45) accessoryView:[_companion _controllerInputTextPanelAccessoryView]]; _inputTextPanel.delegate = self; if (_initialInputText.length != 0) { [_inputTextPanel.inputField setText:_initialInputText]; [_inputTextPanel.inputField selectRange:_initialSelectRange]; _initialInputText = nil; } if (_initialMessageEdigingContext != nil) { [self setEditMessageWithText:_initialMessageEdigingContext.text isCaption:_initialMessageEdigingContext.isCaption messageId:_initialMessageEdigingContext.messageId animated:false]; _initialMessageEdigingContext = nil; } [_inputTextPanel setReplyMarkup:_replyMarkup]; [_inputTextPanel setHasBots:_hasBots]; [_inputTextPanel setCanBroadcast:_canBroadcast]; [_inputTextPanel setIsBroadcasting:_isBroadcasting]; [_inputTextPanel setIsAlwaysBroadcasting:_isAlwaysBroadcasting]; [_inputTextPanel setInputDisabled:_inputDisabled]; [_inputTextPanel setIsChannel:_isChannel]; if (_initialForwardMessages != nil) { TGModernConversationForwardInputPanel *panel = [[TGModernConversationForwardInputPanel alloc] initWithMessages:_initialForwardMessages]; __weak TGModernConversationController *weakSelf = self; panel.dismiss = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf setPrimaryExtendedPanel:nil animated:true]; [strongSelf setSecondaryExtendedPanel:nil animated:true]; } }; [self setPrimaryExtendedPanel:panel animated:true]; } _initialForwardMessages = nil; if (_customInputPanel != nil) [self setInputPanel:_customInputPanel animated:false]; else [self setInputPanel:[self defaultInputPanel] animated:false]; if (_currentTitlePanel != nil) { id currentTitlePanel = _currentTitlePanel; _currentTitlePanel = nil; [self setCurrentTitlePanel:currentTitlePanel animation:TGModernConversationPanelAnimationNone]; } [_view insertSubview:_emptyListPlaceholder belowSubview:_currentInputPanel]; if (![self _updateControllerInset:false]) [self controllerInsetUpdated:UIEdgeInsetsZero]; [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(self.controllerInset.top, 0.0f, _currentInputPanel.frame.size.height, 0.0f) duration:0.0f curve:0]; } - (void)viewWillAppear:(BOOL)animated { TGLog(@"willAppear"); [self setRightBarButtonItem:[self defaultRightBarButtonItem]]; if (self.navigationController.viewControllers.count >= 2) { UIViewController *previousController = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 2]; [_titleView setBackButtonTitle:previousController.navigationItem.backBarButtonItem.title.length == 0 ? TGLocalized(@"Common.Back") : previousController.navigationItem.backBarButtonItem.title]; } if (_didDisappearBeforeAppearing) _keyboardHeight = 0; //else if (_receivedWillDisappear) // _keyboardHeight = _halfTransitionKeyboardHeight; _receivedWillDisappear = false; _inputTextPanel.maybeInputField.internalTextView.enableFirstResponder = false; _willAppearTimestamp = CFAbsoluteTimeGetCurrent(); CGSize collectionViewSize = _view.bounds.size; if (_collectionView != nil) { if (ABS(collectionViewSize.width - _collectionView.frame.size.width) > FLT_EPSILON) [self _performSizeChangesWithDuration:0.0f size:collectionViewSize]; else { [_currentInputPanel adjustForSize:collectionViewSize keyboardHeight:_keyboardHeight duration:0.0 animationCurve:0]; [self _adjustCollectionViewForSize:collectionViewSize keyboardHeight:_keyboardHeight inputContainerHeight:_currentInputPanel.frame.size.height duration:0.0 animationCurve:0]; } } else [_currentInputPanel adjustForSize:collectionViewSize keyboardHeight:_keyboardHeight duration:0.0 animationCurve:0]; [_titleView setOrientation:self.interfaceOrientation]; [_titleView resumeAnimations]; [_avatarButton setOrientation:self.interfaceOrientation]; [super viewWillAppear:animated]; bool beingAnimated = animated; if (_shouldIgnoreAppearAnimationOnce) { beingAnimated = false; _shouldIgnoreAppearAnimationOnce = false; } [_companion _controllerWillAppearAnimated:beingAnimated firstTime:!_alreadyHadWillAppear]; if (_alreadyHadWillAppear) { [self _updateCanRegroupIncomingUnreadMessages]; } _alreadyHadWillAppear = true; NSDictionary *userActivityData = [_companion userActivityData]; if (false && iosMajorVersion() >= 8 && _currentActivity == nil && userActivityData != nil) { NSMutableDictionary *mutableUserActivityData = [[NSMutableDictionary alloc] initWithDictionary:userActivityData]; if (_inputTextPanel.maybeInputField.text.length != 0) mutableUserActivityData[@"text"] = _inputTextPanel.maybeInputField.text; _currentActivity = [[NSUserActivity alloc] initWithActivityType:@"org.telegram.conversation"]; _currentActivity.userInfo = mutableUserActivityData; _currentActivity.webpageURL = [NSURL URLWithString:@"https://telegram.org/dl"]; _currentActivity.delegate = self; _currentActivity.supportsContinuationStreams = true; [_currentActivity becomeCurrent]; } if (!TGIsPad() && iosMajorVersion() >= 8 && _raiseToListenActivator == nil) { __weak TGModernConversationController *weakSelf = self; _raiseToListenActivator = [[TGRaiseToListenActivator alloc] initWithShouldActivate:^bool { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf.associatedWindowStack.count != 0) { return false; } if (strongSelf->_shareSheetWindow != nil || strongSelf->_attachmentSheetWindow != nil) { return false; } return true; } return false; } activate:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf updateRaiseToListen]; if (strongSelf->_raiseToListenActivator.enabled) { if ((TGTelegraphInstance.musicPlayer.playlistMetadata == nil || ![TGTelegraphInstance.musicPlayer.playlistMetadata isEqual:[strongSelf->_companion playlistMetadata:true]]) && strongSelf->_currentAudioRecorder == nil && [strongSelf inputText].length == 0) { if (![strongSelf playNextUnseenIncomingAudio]) { [strongSelf startAudioRecording:true completion:^{}]; } } } } } deactivate:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_currentAudioRecorder != nil) { [strongSelf finishAudioRecording:true]; } } }]; _raiseToListenActivator.enabled = _currentInputPanel == _inputTextPanel; _playlistFinishedDisposable = [[[TGTelegraphInstance.musicPlayer playlistFinished] deliverOn:[SQueue mainQueue]] startWithNext:^(id metadata) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil && [metadata isEqual:[strongSelf->_companion playlistMetadata:true]]) { [strongSelf->_raiseToListenRecordAfterPlaybackTimer invalidate]; strongSelf->_raiseToListenRecordAfterPlaybackTimer = [[TGTimer alloc] initWithTimeout:1.0 repeat:false completion:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_raiseToListenActivator.activated && TGTelegraphInstance.musicPlayer.playlistMetadata == nil) { [strongSelf startAudioRecording:true completion:^{}]; } } } queue:dispatch_get_main_queue()]; [strongSelf->_raiseToListenRecordAfterPlaybackTimer start]; } }]; } } - (void)viewDidAppear:(BOOL)animated { if (animated && _didDisappearBeforeAppearing) { dispatch_async(dispatch_get_main_queue(), ^ { _inputTextPanel.maybeInputField.internalTextView.enableFirstResponder = true; }); } else _inputTextPanel.maybeInputField.internalTextView.enableFirstResponder = true; [_companion _controllerDidAppear:!_alreadyHadDidAppear]; _alreadyHadDidAppear = true; [self _updateCanReadHistory:TGModernConversationActivityChangeActive]; _companion.viewContext.animationsEnabled = true; [self _updateItemsAnimationsEnabled]; [super viewDidAppear:animated]; if (_shouldOpenKeyboardOnce) { _inputTextPanel.inputField.internalTextView.enableFirstResponder = true; _shouldOpenKeyboardOnce = false; [self openKeyboard]; } _inputTextPanel.canShowKeyboardAutomatically = true; _inputTextPanel.enableKeyboard = true; [self maybeDisplayGifTooltip]; } - (void)previewAudioWithDataItem:(TGDataItem *)dataItem duration:(NSTimeInterval)duration liveUploadData:(TGLiveUploadActorData *)liveUploadData waveform:(TGAudioWaveform *)waveform { __weak TGModernConversationController *weakSelf = self; TGModernConversationAudioPreviewInputPanel *panel = [[TGModernConversationAudioPreviewInputPanel alloc] initWithDataItem:dataItem duration:duration liveUploadActorData:liveUploadData waveform:waveform cancel:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf setInputPanel:strongSelf->_customInputPanel != nil ? strongSelf->_customInputPanel : [strongSelf defaultInputPanel] animated:true]; } } send:^(TGDataItem *dataItem, NSTimeInterval duration, TGLiveUploadActorData *liveUploadData, TGAudioWaveform *waveform) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf setInputPanel:strongSelf->_customInputPanel != nil ? strongSelf->_customInputPanel : [strongSelf defaultInputPanel] animated:true]; [strongSelf->_companion controllerWantsToSendLocalAudioWithDataItem:dataItem duration:duration liveData:liveUploadData waveform:waveform asReplyToMessageId:[strongSelf currentReplyMessageId]]; } }]; panel.delegate = self; panel.playbackDidBegin = ^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf stopAudioRecording]; [strongSelf stopInlineMediaIfPlaying:false]; [TGTelegraphInstance.musicPlayer controlPause]; } }; id inputPanel = [_inputTextPanel primaryExtendedPanel]; if ([inputPanel isKindOfClass:[TGModernConversationForwardInputPanel class]]) { inputPanel = nil; } [panel setPrimaryExtendedPanel:inputPanel animated:false]; [panel setSecondaryExtendedPanel:[_inputTextPanel secondaryExtendedPanel] animated:false]; [self setInputPanel:panel animated:true]; } - (void)applicationWillResignActive:(NSNotification *)__unused notification { [self _updateCanReadHistory:TGModernConversationActivityChangeInactive]; if (_currentAudioRecorder != nil) { [self finishAudioRecording:true]; } } - (void)applicationDidEnterBackground:(NSNotification *)__unused notification { [self _updateCanReadHistory:TGModernConversationActivityChangeInactive]; } - (void)applicationDidBecomeActive:(NSNotification *)__unused notification { [self _updateCanRegroupIncomingUnreadMessages]; [self _updateCanReadHistory:TGModernConversationActivityChangeAuto]; } - (void)serviceNotificationReceived:(NSNotification *)__unused notification { if (self.navigationController.topViewController == self && self.presentedViewController == nil) { for (UIWindow *window in [[UIApplication sharedApplication] windows]) { if ([window isKindOfClass:[TGOverlayControllerWindow class]]) { TGOverlayControllerWindow *overlayControllerWindow = (TGOverlayControllerWindow *)window; if ([overlayControllerWindow.rootViewController isKindOfClass:[TGModernGalleryController class]]) { TGModernGalleryController *galleryController = (TGModernGalleryController *)overlayControllerWindow.rootViewController; if ([galleryController isFullyOpaque]) return; } } } } else return; NSMutableArray *messageIds = [[NSMutableArray alloc] init]; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil) [messageIds addObject:@(item->_message.mid)]; } [_companion serviceNotificationsForMessageIds:messageIds]; } - (void)viewWillDisappear:(BOOL)animated { _inputTextPanel.enableKeyboard = false; _inputTextPanel.canShowKeyboardAutomatically = true; _didDisappearBeforeAppearing = false; _receivedWillDisappear = true; freedomUIKitTest4_1(); [self stopInlineMediaIfPlaying]; [_collectionView stopScrollingAnimation]; [self _updateCanReadHistory:TGModernConversationActivityChangeInactive]; [self stopInlineMedia]; _companion.viewContext.animationsEnabled = false; [self _updateItemsAnimationsEnabled]; if (iosMajorVersion() >= 8) { [_currentActivity invalidate]; _currentActivity = nil; } if (_shareSheetWindow != nil) [_shareSheetWindow dismissAnimated:animated completion:nil]; _dropboxProxy = nil; [_recentGifsDisposable setDisposable:nil]; [_tooltipContainerView removeFromSuperview]; _tooltipContainerView = nil; if (self.isMovingFromParentViewController && _menuController != nil) [_menuController dismissAnimated:false]; [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { _receivedWillDisappear = false; _didDisappearBeforeAppearing = true; _keyboardHeight = 0.0f; [self _updateCanReadHistory:TGModernConversationActivityChangeInactive]; [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:_keyboardHeight duration:0.0 animationCurve:0]; [self _adjustCollectionViewForSize:_view.bounds.size keyboardHeight:_keyboardHeight inputContainerHeight:_currentInputPanel.frame.size.height duration:0.0 animationCurve:0]; [_inputTextPanel.maybeInputField.internalTextView resignFirstResponder]; [_titleView suspendAnimations]; [self _leaveEditingModeAnimated:false]; [_companion updateControllerInputText:_inputTextPanel.maybeInputField.text messageEditingContext:_inputTextPanel.messageEditingContext]; [super viewDidDisappear:animated]; } - (BOOL)shouldAutorotate { bool tracking = _collectionView.isTracking; return !tracking && [super shouldAutorotate] && _currentAudioRecorder == nil; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; _isRotating = true; if (_keyboardHeight < FLT_EPSILON) { //[self _performSizeChangesWithDuration:duration size:_view.bounds.size]; } } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; _isRotating = false; } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [_titleView setOrientation:toInterfaceOrientation]; [_avatarButton setOrientation:self.interfaceOrientation]; [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; } - (void)controllerInsetUpdated:(UIEdgeInsets)previousInset { [super controllerInsetUpdated:previousInset]; if (![self viewControllerIsChangingInterfaceOrientation] && _collectionView != nil) [self _adjustCollectionInset]; if (_menuContainerView != nil) { _menuContainerView.frame = CGRectMake(0, self.controllerInset.top, _view.frame.size.width, _view.frame.size.height - self.controllerInset.top - self.controllerInset.bottom); } if (_tooltipContainerView != nil) { _tooltipContainerView.frame = CGRectMake(0, self.controllerInset.top, _view.frame.size.width, _view.frame.size.height - self.controllerInset.top - self.controllerInset.bottom); } if (![self viewControllerIsChangingInterfaceOrientation]) { if (_titlePanelWrappingView != nil) { _titlePanelWrappingView.frame = CGRectMake(0.0f, self.controllerInset.top, _view.frame.size.width, _titlePanelWrappingView.frame.size.height); } } } - (void)_adjustCollectionInset { UIEdgeInsets contentInset = _collectionView.contentInset; if (ABS(contentInset.bottom - self.controllerInset.top) > FLT_EPSILON) { contentInset.bottom = self.controllerInset.top + 210.0f + [_collectionView implicitTopInset]; _collectionView.contentInset = contentInset; [self _updateUnseenMessagesButton]; [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset], 0.0f, _collectionView.contentInset.top, 0.0f) duration:0.0f curve:0]; } } - (void)stopAudioRecording { if (_currentAudioRecorder != nil) { _currentAudioRecorder.delegate = nil; [_currentAudioRecorder cancel]; _currentAudioRecorder = nil; _currentAudioRecorderIsTouchInitiated = false; if ([self shouldAutorotate]) [TGViewController attemptAutorotation]; } [_raiseToListenRecordAfterPlaybackTimer invalidate]; _raiseToListenRecordAfterPlaybackTimer = nil; [self updateRaiseToListen]; } - (void)stopInlineMediaIfPlaying { [self stopInlineMediaIfPlaying:true]; } - (void)stopInlineMediaIfPlaying:(bool)stopPreview { [_raiseToListenRecordAfterPlaybackTimer invalidate]; _raiseToListenRecordAfterPlaybackTimer = nil; if (stopPreview && [_currentInputPanel isKindOfClass:[TGModernConversationAudioPreviewInputPanel class]]) { [(TGModernConversationAudioPreviewInputPanel *)_currentInputPanel stop]; } } - (void)touchedTableBackground { if (_menuContainerView.isShowingMenu) return; if ([_inputTextPanel.maybeInputField.internalTextView isFirstResponder]) [_inputTextPanel.maybeInputField.internalTextView resignFirstResponder]; [_searchBar resignFirstResponder]; } - (void)tableTouchesCancelled { for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil) [item clearHighlights]; } } - (void)navigationBarAction { [_menuContainerView hideMenu]; [_tooltipContainerView hideMenu]; } - (void)avatarPressed { [_companion _controllerAvatarPressed]; } - (void)closeButtonPressed { [_companion _dismissController]; } - (void)infoButtonPressed { [self avatarPressed]; } - (void)setInputPanel:(TGModernConversationInputPanel *)panel animated:(bool)animated { if (!self.isViewLoaded || _companion.previewMode) return; if (panel == _currentInputPanel) return; if (animated) { TGModernConversationInputPanel *previousPanel = _currentInputPanel; _currentInputPanel = nil; int curve = iosMajorVersion() < 7 ? 0 : 7; _currentInputPanel = panel; if (_currentInputPanel != nil) { [_view addSubview:_currentInputPanel]; [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:_keyboardHeight duration:0.0 animationCurve:0]; _currentInputPanel.frame = CGRectMake(_currentInputPanel.frame.origin.x, _view.frame.size.height, _currentInputPanel.frame.size.width, _currentInputPanel.frame.size.height); } [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:_keyboardHeight duration:0.3 animationCurve:curve]; [self _adjustCollectionViewForSize:_view.bounds.size keyboardHeight:_keyboardHeight inputContainerHeight:_currentInputPanel.frame.size.height duration:0.3 animationCurve:curve]; [previousPanel endEditing:true]; [UIView animateWithDuration:0.22 delay:0.00 options:0 animations:^ { previousPanel.frame = CGRectMake(0.0f, _view.frame.size.height, previousPanel.frame.size.width, previousPanel.frame.size.height); } completion:^(__unused BOOL finished) { [previousPanel removeFromSuperview]; }]; } else { [_currentInputPanel removeFromSuperview]; _currentInputPanel = panel; if (_currentInputPanel != nil) { [_view addSubview:_currentInputPanel]; [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:_keyboardHeight duration:0.0 animationCurve:0]; _currentInputPanel.frame = CGRectMake(_currentInputPanel.frame.origin.x, _view.frame.size.height - _currentInputPanel.frame.size.height, _currentInputPanel.frame.size.width, _currentInputPanel.frame.size.height); [self _adjustCollectionViewForSize:_view.bounds.size keyboardHeight:_keyboardHeight inputContainerHeight:_currentInputPanel.frame.size.height duration:0.0 animationCurve:0]; } } [self updateRaiseToListen]; } #pragma mark - - (NSArray *)items { return _items; } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { if (collectionView == _collectionView) return 1; return 0; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (collectionView == _collectionView && section == 0) return _items.count; return 0; } - (void)_updateVisibleItemIndices:(TGModernCollectionCell *)additionalCell { if (!_disableScrollProcessing) { if (_messageIdForVisibleHoleDirection != 0 && !_enableBelowHistoryRequests && _collectionView.contentOffset.y <= -_collectionView.contentInset.top + 10.0f && [_collectionView visibleCells].count != 0) { _messageIdForVisibleHoleDirection = 0; } } NSMutableArray *sortedHoles = nil; NSMutableArray *currentMessageIds = nil; for (TGModernCollectionCell *cell in [_collectionView visibleCells]) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil) { if (item->_message.hole != nil) { if (sortedHoles == nil) { sortedHoles = [[NSMutableArray alloc] init]; } TGVisibleMessageHoleDirection direction = TGVisibleMessageHoleDirectionEarlier; int32_t sortDistance = 0; if (_messageIdForVisibleHoleDirection != 0 && _messageIdForVisibleHoleDirection <= ABS(item->_message.mid)) { direction = TGVisibleMessageHoleDirectionLater; } else { direction = TGVisibleMessageHoleDirectionEarlier; } if (_messageIdForVisibleHoleDirection != 0) { sortDistance = ABS(_messageIdForVisibleHoleDirection - ABS(item->_message.mid)); } else { sortDistance = INT32_MAX - ABS(item->_message.mid); } NSInteger index = -1; bool added = false; for (TGVisibleMessageHole *currentHole in sortedHoles) { index++; int32_t currentDistance = 0; if (_messageIdForVisibleHoleDirection != 0) { currentDistance = ABS(_messageIdForVisibleHoleDirection - currentHole.hole.maxId); } else { currentDistance = INT32_MAX - currentHole.hole.maxId; } if (currentDistance > sortDistance) { [sortedHoles insertObject:[[TGVisibleMessageHole alloc] initWithHole:item->_message.hole direction:direction] atIndex:index]; added = true; break; } } if (!added) { [sortedHoles addObject:[[TGVisibleMessageHole alloc] initWithHole:item->_message.hole direction:direction]]; } } if (item->_message.viewCount != 0 && item->_message.mid < TGMessageLocalMidBaseline) { if (currentMessageIds == nil) { currentMessageIds = [[NSMutableArray alloc] init]; } [currentMessageIds addObject:@(item->_message.mid)]; } } } if (additionalCell != nil) { TGMessageModernConversationItem *item = additionalCell.boundItem; if (item != nil) { if (item->_message.hole != nil) { if (sortedHoles == nil) { sortedHoles = [[NSMutableArray alloc] init]; } TGVisibleMessageHoleDirection direction = TGVisibleMessageHoleDirectionEarlier; int32_t sortDistance = 0; if (_messageIdForVisibleHoleDirection != 0 && _messageIdForVisibleHoleDirection <= ABS(item->_message.mid)) { direction = TGVisibleMessageHoleDirectionLater; } else { direction = TGVisibleMessageHoleDirectionEarlier; } if (_messageIdForVisibleHoleDirection != 0) { sortDistance = ABS(_messageIdForVisibleHoleDirection - ABS(item->_message.mid)); } else { sortDistance = INT32_MAX - ABS(item->_message.mid); } NSInteger index = -1; bool added = false; for (TGVisibleMessageHole *currentHole in sortedHoles) { index++; int32_t currentDistance = 0; if (_messageIdForVisibleHoleDirection != 0) { currentDistance = ABS(_messageIdForVisibleHoleDirection - currentHole.hole.maxId); } else { currentDistance = INT32_MAX - currentHole.hole.maxId; } if (currentDistance > sortDistance) { [sortedHoles insertObject:[[TGVisibleMessageHole alloc] initWithHole:item->_message.hole direction:direction] atIndex:index]; added = true; break; } } if (!added) { [sortedHoles addObject:[[TGVisibleMessageHole alloc] initWithHole:item->_message.hole direction:direction]]; } } if (item->_message.viewCount != 0 && item->_message.mid < TGMessageLocalMidBaseline) { if (currentMessageIds == nil) { currentMessageIds = [[NSMutableArray alloc] init]; } [currentMessageIds addObject:@(item->_message.mid)]; } } } [_companion _controllerDidUpdateVisibleHoles:sortedHoles]; if (currentMessageIds != nil) { [_companion _controllerDidUpdateVisibleUnseenMessageIds:currentMessageIds]; } if (_collectionView.contentSize.height > FLT_EPSILON && !_disableScrollProcessing) { if ((NSInteger)_items.count >= TGModernConversationControllerUnloadHistoryLimit + TGModernConversationControllerUnloadHistoryThreshold) [self _maybeUnloadHistory]; if (_enableAboveHistoryRequests && _collectionView.contentOffset.y > _collectionView.contentSize.height - 800 * 2.0f && _collectionView.contentSize.height > FLT_EPSILON) [_companion loadMoreMessagesAbove]; if (_enableBelowHistoryRequests && _collectionView.contentOffset.y < 600 * 2.0f) [_companion loadMoreMessagesBelow]; } } - (void)collectionView:(UICollectionView *)__unused collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)__unused indexPath { if (_temporaryHighlightMessageIdUponDisplay != 0) { TGMessageModernConversationItem *item = [(TGModernCollectionCell *)cell boundItem]; if (item != nil && item->_message.mid == _temporaryHighlightMessageIdUponDisplay) { _temporaryHighlightMessageIdUponDisplay = 0; [item setTemporaryHighlighted:true viewStorage:_viewStorage]; TGDispatchAfter(0.6, dispatch_get_main_queue(), ^ { [item setTemporaryHighlighted:false viewStorage:_viewStorage]; }); } } [self _updateVisibleItemIndices:(TGModernCollectionCell *)cell]; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { TGModernConversationItem *item = indexPath.row < (NSInteger)_items.count ? [_items objectAtIndex:indexPath.row] : nil; if (item != nil) { __block TGModernCollectionCell *cell = nil; [UIView performWithoutAnimation:^ { cell = [item dequeueCollectionCell:collectionView registeredIdentifiers:_collectionRegisteredIdentifiers forIndexPath:indexPath]; if (cell.boundItem != nil) { TGModernConversationItem *item = cell.boundItem; [item unbindCell:_viewStorage]; } if (!_disableItemBinding) [self _bindItem:item toCell:cell atIndexPath:indexPath]; }]; return cell; } } return [collectionView dequeueReusableCellWithReuseIdentifier:@"_empty" forIndexPath:indexPath]; } - (void)_bindItem:(TGModernConversationItem *)item toCell:(TGModernCollectionCell *)cell atIndexPath:(NSIndexPath *)__unused indexPath { bool movedFromTemporaryContainer = false; if (_itemsBoundToTemporaryContainer != nil && [_itemsBoundToTemporaryContainer containsObject:item]) { [_itemsBoundToTemporaryContainer removeObject:item]; [item moveToCell:cell]; movedFromTemporaryContainer = true; #if TGModernConversationControllerLogCellOperations TGLog(@"(restore item %d)", indexPath.item); #endif } if (!movedFromTemporaryContainer) { #if TGModernConversationControllerLogCellOperations TGLog(@"dequeue cell at %d (bind)", indexPath.item); #endif if (item.boundCell != nil) [item unbindCell:_viewStorage]; [item bindCell:cell viewStorage:_viewStorage]; } } - (void)collectionView:(UICollectionView *)__unused collectionView didEndDisplayingCell:(TGModernCollectionCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { if (cell.boundItem != nil) { #if TGModernConversationControllerLogCellOperations TGLog(@"enqueue cell at %d (unbind)", indexPath.item); #endif TGModernConversationItem *item = cell.boundItem; [item unbindCell:_viewStorage]; } else { #if TGModernConversationControllerLogCellOperations TGLog(@"enqueue cell at %d (clear)", indexPath.item); #endif } } [self _updateVisibleItemIndices:nil]; } #pragma mark - - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { if (scrollView == _collectionView) { //if (!_disableScrollProcessing) // _disableScrollProcessing = false; } } - (void)_checkScrollBackMessage { if (_scrollBackMessageId != 0 && _canCheckScrollBackMessageId) { bool found = false; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil && item->_message.mid == _scrollBackMessageId) { found = true; [self setScrollBackMessageId:0]; break; } } if (!found) [self setScrollBackButtonVisible:true]; } } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (scrollView == _collectionView) { if (_unseenMessagesButton != nil && _unseenMessagesButton.superview != nil && scrollView.contentOffset.y <= -scrollView.contentInset.top) { [self setHasUnseenMessagesBelow:false]; } if (_scrollBackMessageId != 0) [self _checkScrollBackMessage]; if (scrollView.contentSize.height > FLT_EPSILON && !_disableScrollProcessing) { if ((NSInteger)_items.count >= TGModernConversationControllerUnloadHistoryLimit + TGModernConversationControllerUnloadHistoryThreshold) [self _maybeUnloadHistory]; if (_enableAboveHistoryRequests && scrollView.contentOffset.y > scrollView.contentSize.height - 800 * 2.0f && scrollView.contentSize.height > FLT_EPSILON) [_companion loadMoreMessagesAbove]; if (_enableBelowHistoryRequests && scrollView.contentOffset.y < 600 * 2.0f) [_companion loadMoreMessagesBelow]; } } } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)__unused scrollView { _scrollingToBottom = nil; _canCheckScrollBackMessageId = true; [self _checkScrollBackMessage]; } - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { if (scrollView == _collectionViewScrollToTopProxy) { [_collectionView scrollToTopIfNeeded]; return false; } return false; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer { return true; } - (void)collectionViewPan:(UIPanGestureRecognizer *)__unused recognizer { } #pragma mark - - (NSArray *)_currentItems { return _items; } - (void)replaceItems:(NSArray *)newItems messageIdForVisibleHoleDirection:(int32_t)messageIdForVisibleHoleDirection { _messageIdForVisibleHoleDirection = messageIdForVisibleHoleDirection; [_items removeAllObjects]; [_items addObjectsFromArray:newItems]; if (self.isViewLoaded) { _collectionView.unreadMessageRange = _companion.unreadMessageRange; [_collectionView reloadData]; } } - (void)addScaleAnimation:(CALayer *)layer delay:(NSTimeInterval)delay { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; animation.fromValue = @(0.95f); animation.toValue = @(1.0f); animation.duration = 0.35 * TGAnimationSpeedFactor(); animation.beginTime = CACurrentMediaTime() + delay; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; animation.removedOnCompletion = true; animation.fillMode = kCAFillModeBoth; [layer addAnimation:animation forKey:@"transform.scale"]; } - (void)addAlphaAnimation:(CALayer *)layer delay:(NSTimeInterval)delay { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; animation.fromValue = @(0.0f); animation.toValue = @(1.0f); animation.duration = 0.31 * TGAnimationSpeedFactor(); animation.beginTime = CACurrentMediaTime() + delay; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; animation.removedOnCompletion = true; animation.fillMode = kCAFillModeBoth; [layer addAnimation:animation forKey:@"opacity"]; } - (void)addOffsetAnimation:(CALayer *)layer delay:(NSTimeInterval)delay offset:(CGFloat)offset { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(layer.position.x, layer.position.y + offset)]; animation.toValue = [NSValue valueWithCGPoint:layer.position]; animation.duration = 0.31 * TGAnimationSpeedFactor(); animation.beginTime = CACurrentMediaTime() + delay; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; animation.removedOnCompletion = true; animation.fillMode = kCAFillModeBoth; [layer addAnimation:animation forKey:@"position"]; } - (TGMessage *)latestVisibleMessage { TGMessage *latestMessage = nil; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil) { if (latestMessage == nil || (int32_t)item->_message.date > (int32_t)latestMessage.date || ((int32_t)item->_message.date == (int32_t)latestMessage.date && ABS(item->_message.mid) > ABS(latestMessage.mid))) { latestMessage = item->_message; } } } return latestMessage; } - (NSArray *)visibleMessageIds { NSMutableArray *messageIds = [[NSMutableArray alloc] init]; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil) { [messageIds addObject:@(item->_message.mid)]; } } return messageIds; } - (void)replaceItems:(NSArray *)newItems positionAtMessageId:(int32_t)positionAtMessageId expandAt:(int32_t)expandMessageId jump:(bool)jump top:(bool)top messageIdForVisibleHoleDirection:(int32_t)messageIdForVisibleHoleDirection scrollBackMessageId:(int32_t)scrollBackMessageId animated:(bool)animated { _messageIdForVisibleHoleDirection = positionAtMessageId; if (messageIdForVisibleHoleDirection != 0) { _messageIdForVisibleHoleDirection = messageIdForVisibleHoleDirection; } NSMutableDictionary *storedViewFrames = [[NSMutableDictionary alloc] init]; NSMutableDictionary *storedDecorationViewFrames = [[NSMutableDictionary alloc] init]; NSMutableDictionary *storedViews = [[NSMutableDictionary alloc] init]; NSMutableDictionary *storedDecorationViews = [[NSMutableDictionary alloc] init]; NSMutableArray *previousGroups = [[NSMutableArray alloc] init]; int32_t minVisiblePreviousMid = INT32_MAX; int32_t maxVisiblePreviousMid = 0; for (TGModernCollectionCell *cell in [_collectionView visibleCells]) { TGMessageModernConversationItem *item = [cell boundItem]; if (item != nil) { if (item->_message.group != nil) { [previousGroups addObject:item->_message.group]; } storedViewFrames[@(item->_message.mid)] = [NSValue valueWithCGRect:[_view convertRect:cell.frame fromView:_collectionView]]; storedViews[@(item->_message.mid)] = [cell snapshotViewAfterScreenUpdates:false]; minVisiblePreviousMid = MIN(minVisiblePreviousMid, ABS(item->_message.mid)); maxVisiblePreviousMid = MAX(maxVisiblePreviousMid, ABS(item->_message.mid)); } } for (UIView *decorationView in [_collectionView visibleDecorations]) { if ([decorationView isKindOfClass:[TGModernDateHeaderView class]]) { TGModernDateHeaderView *dateHeader = (TGModernDateHeaderView *)decorationView; storedDecorationViewFrames[@(dateHeader.date)] = [NSValue valueWithCGRect:[_view convertRect:dateHeader.frame fromView:_collectionView]]; storedDecorationViews[@(dateHeader.date)] = [dateHeader snapshotViewAfterScreenUpdates:false]; } } NSMutableDictionary *previousItemsByMessageId = [[NSMutableDictionary alloc] init]; for (TGMessageModernConversationItem *item in _items) { previousItemsByMessageId[@(item->_message.mid)] = item; } [_items removeAllObjects]; [_items addObjectsFromArray:newItems]; for (NSUInteger i = 0; i < _items.count; i++) { TGMessageModernConversationItem *currentItem = _items[i]; TGMessageModernConversationItem *previousItem = previousItemsByMessageId[@(currentItem->_message.mid)]; if (previousItem != nil) { [_items replaceObjectAtIndex:i withObject:previousItem]; } } if (self.isViewLoaded) { _collectionView.unreadMessageRange = _companion.unreadMessageRange; _disableScrollProcessing = true; [self _beginReloadDataWithTemporaryContainer]; NSInteger index = -1; CGFloat offsetDifference = 0.0f; for (TGMessageModernConversationItem *item in _items) { index++; if (item->_message.mid == positionAtMessageId) { UICollectionViewLayoutAttributes *layoutAttributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; NSValue *previousFrame = storedViewFrames[@(item->_message.mid)]; if (layoutAttributes != nil && previousFrame != nil) { CGRect updatedFrame = [_view convertRect:layoutAttributes.frame fromView:_collectionView]; offsetDifference = updatedFrame.origin.y - [previousFrame CGRectValue].origin.y; } break; } } CGFloat contentOffsetY = _collectionView.contentOffset.y - offsetDifference; if (expandMessageId != 0) { TGMessageGroup *expandGroup = nil; for (TGMessageGroup *group in previousGroups) { if (ABS(expandMessageId) >= group.minId && ABS(expandMessageId) <= group.maxId) { expandGroup = group; break; } } if (expandGroup != nil) { CGFloat lastItemOffsetY = 0.0; for (TGMessageModernConversationItem *item in _items) { if (item->_message.mid <= expandGroup.maxId) { lastItemOffsetY = [self contentOffsetForMessageId:item->_message.mid scrollPosition:TGInitialScrollPositionBottom initial:true additionalOffset:0.0f]; break; } } if (contentOffsetY > lastItemOffsetY) { for (TGMessageModernConversationItem *item in _items.reverseObjectEnumerator) { if (item->_message.mid >= expandGroup.minId) { contentOffsetY = MIN(contentOffsetY, [self contentOffsetForMessageId:item->_message.mid scrollPosition:TGInitialScrollPositionTop initial:true additionalOffset:100.0f]); break; } } } } } if (jump) { contentOffsetY = [self contentOffsetForMessageId:positionAtMessageId scrollPosition:TGInitialScrollPositionCenter initial:true additionalOffset:0.0f]; } else if (top) { contentOffsetY = [self contentOffsetForMessageId:positionAtMessageId scrollPosition:TGInitialScrollPositionTop initial:true additionalOffset:0.0f]; } if (contentOffsetY > _collectionLayout.collectionViewContentSize.height + _collectionView.contentInset.bottom - _collectionView.frame.size.height) { contentOffsetY = _collectionLayout.collectionViewContentSize.height + _collectionView.contentInset.bottom - _collectionView.frame.size.height; } if (contentOffsetY < -_collectionView.contentInset.top) { contentOffsetY = -_collectionView.contentInset.top; } [_collectionView setContentOffset:CGPointMake(0.0f, contentOffsetY) animated:false]; [_collectionView setNeedsLayout]; [_collectionView layoutSubviews]; [self _endReloadDataWithTemporaryContainer]; _disableScrollProcessing = false; NSMutableArray *visibleMids = [[NSMutableArray alloc] init]; CGFloat minY = 0.0; CGFloat maxY = 0.0; int32_t minVisibleMid = INT32_MAX; int32_t maxVisibleMid = 0; NSMutableArray *currentGroups = [[NSMutableArray alloc] init]; for (TGModernCollectionCell *cell in [_collectionView visibleCells]) { TGMessageModernConversationItem *item = [cell boundItem]; if (item != nil) { if (ABS(minY) < FLT_EPSILON || cell.frame.origin.y < minY) { minY = cell.frame.origin.y; } if (ABS(maxY) < FLT_EPSILON || CGRectGetMaxY(cell.frame) > maxY) { maxY = CGRectGetMaxY(cell.frame); } int32_t mid = item->_message.mid; minVisibleMid = MIN(minVisibleMid, ABS(mid)); maxVisibleMid = MAX(maxVisibleMid, ABS(mid)); if (item->_message.group != nil) { [currentGroups addObject:item->_message.group]; } } } for (TGModernCollectionCell *cell in [_collectionView visibleCells]) { TGMessageModernConversationItem *item = [cell boundItem]; if (item != nil) { [visibleMids addObject:@(item->_message.mid)]; NSValue *previousFrame = nil; int32_t mid = item->_message.mid; TGMessageGroup *group = nil; for (TGMessageGroup *currentGroup in previousGroups) { if (mid >= currentGroup.minId && mid <= currentGroup.maxId) { group = currentGroup; } } bool grouped = false; if (group != nil && storedViewFrames[@(-group.maxId)] != nil) { previousFrame = storedViewFrames[@(-group.maxId)]; grouped = true; } else { previousFrame = storedViewFrames[@(item->_message.mid)]; } if (previousFrame == nil && item->_message.group != nil) { int32_t minId = item->_message.group.minId; int32_t maxId = item->_message.group.maxId; __block int32_t selectedId = 0; __block NSValue *resultFrame = nil; [storedViewFrames enumerateKeysAndObjectsUsingBlock:^(NSNumber *nMid, NSValue *nFrame, __unused BOOL *stop) { int32_t mid = [nMid intValue]; if (mid >= minId && mid <= maxId) { if (selectedId == 0 || mid > selectedId) { selectedId = mid; resultFrame = nFrame; } } }]; previousFrame = resultFrame; } CGRect frame = cell.frame; if (previousFrame != nil) { CGRect updatedFrame = [_view convertRect:[previousFrame CGRectValue] toView:_collectionView]; if (!grouped) { updatedFrame.size = frame.size; cell.frame = updatedFrame; if (animated) { [UIView animateWithDuration:0.25 delay:0.0 options:7 << 16 animations:^{ cell.frame = frame; } completion:nil]; } else { cell.frame = frame; } if (item->_message.group != nil && animated) { [self addAlphaAnimation:cell.layer delay:0.0]; } } else { CGFloat distance = ABS(updatedFrame.origin.y - frame.origin.y) / _collectionView.frame.size.height; NSTimeInterval delay = MIN(distance / 1.5f, 0.25); if (animated) { [self addAlphaAnimation:cell.layer delay:delay]; [self addScaleAnimation:cell.layer delay:delay]; } if (ABS(updatedFrame.origin.y - frame.origin.y) > 5.0f && animated) { if (updatedFrame.origin.y < frame.origin.y) { [self addOffsetAnimation:cell.layer delay:delay * 0.7 offset:-8.0f]; } else { [self addOffsetAnimation:cell.layer delay:delay * 0.7 offset:8.0f]; } } } } else { CGRect offsetFrame = cell.frame; if (ABS(mid) < minVisiblePreviousMid) { offsetFrame.origin.y = maxY + 1000.0f; } else if (ABS(mid) > maxVisiblePreviousMid) { offsetFrame.origin.y = minY - 1000.0f; } cell.frame = offsetFrame; if (animated) { [UIView animateWithDuration:0.25 delay:0.0 options:7 << 16 animations:^{ cell.frame = frame; } completion:nil]; [self addAlphaAnimation:cell.layer delay:0.0]; } else { cell.frame = frame; } } } } [storedViews removeObjectsForKeys:visibleMids]; [storedViews enumerateKeysAndObjectsUsingBlock:^(NSNumber *nMid, UIView *cell, __unused BOOL *stop) { NSValue *previousFrame = nil; int32_t mid = [nMid intValue]; TGMessageGroup *group = nil; for (TGMessageGroup *currentGroup in currentGroups) { if (mid >= currentGroup.minId && mid <= currentGroup.maxId) { group = currentGroup; } } previousFrame = storedViewFrames[@(mid)]; if (previousFrame != nil) { CGRect updatedFrame = [_view convertRect:[previousFrame CGRectValue] toView:_collectionView]; cell.frame = updatedFrame; [_collectionView insertSubview:cell atIndex:0]; CGRect offsetFrame = updatedFrame; if (mid > 0 && group == nil) { if (ABS(mid) < minVisibleMid) { offsetFrame.origin.y = maxY + 1000.0f; } else { offsetFrame.origin.y = minY - 1000.0f; } } if (animated) { [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ cell.alpha = 0.0f; cell.frame = offsetFrame; } completion:^(__unused BOOL finished){ [cell removeFromSuperview]; }]; } else { cell.alpha = 0.0f; cell.frame = offsetFrame; [cell removeFromSuperview]; } } }]; NSMutableArray *visibleDecorationViewIds = [[NSMutableArray alloc] init]; for (UIView *decorationView in [_collectionView visibleDecorations]) { if ([decorationView isKindOfClass:[TGModernDateHeaderView class]]) { TGModernDateHeaderView *dateView = (TGModernDateHeaderView *)decorationView; NSValue *previousFrame = storedDecorationViewFrames[@(dateView.date)]; CGRect frame = dateView.frame; if (previousFrame != nil) { CGRect updatedFrame = [_view convertRect:[previousFrame CGRectValue] toView:_collectionView]; updatedFrame.size = frame.size; dateView.frame = updatedFrame; if (animated) { [UIView animateWithDuration:0.25 delay:0.0 options:7 << 16 animations:^{ dateView.frame = frame; } completion:nil]; } else { dateView.frame = frame; } } } } [storedDecorationViews removeObjectsForKeys:visibleDecorationViewIds]; [self setScrollBackMessageId:scrollBackMessageId]; if (scrollBackMessageId != 0) [self setScrollBackButtonVisible:true]; [self _updateVisibleItemIndices:nil]; } } - (void)replaceItemsWithFastScroll:(NSArray *)newItems intent:(TGModernConversationInsertItemIntent)intent scrollToMessageId:(int32_t)scrollToMessageId scrollBackMessageId:(int32_t)scrollBackMessageId animated:(bool)animated { bool scrollDown = true; if (scrollToMessageId != 0) { bool allMessagesAreBelow = true; for (TGMessageModernConversationItem *item in _items) { if (item->_message.mid > scrollToMessageId) { allMessagesAreBelow = false; break; } } scrollDown = allMessagesAreBelow; } NSMutableArray *storedCells = [[NSMutableArray alloc] init]; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { if (cell.boundItem != nil) { TGModernCollectionCell *cellCopy = [[TGModernCollectionCell alloc] initWithFrame:[_collectionView convertRect:cell.frame toView:_view]]; [(TGMessageModernConversationItem *)cell.boundItem moveToCell:cellCopy]; [storedCells addObject:cellCopy]; } } if (animated) _temporaryHighlightMessageIdUponDisplay = scrollToMessageId; [_items removeAllObjects]; [_collectionView reloadData]; if (storedCells.count != 0) { if (intent == TGModernConversationInsertItemIntentSendTextMessage) { _inputTextPanel.maybeInputField.oneTimeLongAnimation = true; [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:_inputTextPanel.maybeInputField.text.length != 0]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:_inputTextPanel.maybeInputField.text.length != 0]; [_inputTextPanel.maybeInputField setText:@"" animated:true]; } else if (intent == TGModernConversationInsertItemIntentSendOtherMessage) { if ([self currentReplyMessageId] != 0) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; } } [_items addObjectsFromArray:newItems]; [_collectionView reloadData]; if (scrollToMessageId) { TGMessageModernConversationItem *selectedItem = nil; for (TGMessageModernConversationItem *messageItem in _items) { if (messageItem->_message.mid == scrollToMessageId) { selectedItem = messageItem; break; } } if (selectedItem != nil) { _scrollingToBottom = @false; [_collectionView setContentOffset:CGPointMake(0.0f, [self contentOffsetForMessageId:scrollToMessageId scrollPosition:TGInitialScrollPositionCenter initial:false additionalOffset:0.0f]) animated:false]; } else { if (_collectionView.contentOffset.y > -_collectionView.contentInset.top) { [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true]; _scrollingToBottom = @true; } } } else { _scrollingToBottom = nil; [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:false]; } NSMutableArray *currentCellsWithFrames = [[NSMutableArray alloc] init]; CGFloat minStoredCellY = CGFLOAT_MAX; CGFloat maxStoredCellY = CGFLOAT_MIN; for (TGModernCollectionCell *cell in storedCells) { cell.frame = [_collectionView convertRect:cell.frame fromView:_view]; minStoredCellY = MIN(minStoredCellY, cell.frame.origin.y); maxStoredCellY = MAX(maxStoredCellY, CGRectGetMaxY(cell.frame)); [_collectionView addSubview:cell]; } [_collectionView layoutSubviews]; CGFloat maxCurrentCellY = CGFLOAT_MIN; CGFloat minCurrentCellY = CGFLOAT_MAX; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { maxCurrentCellY = MAX(maxCurrentCellY, CGRectGetMaxY(cell.frame)); minCurrentCellY = MIN(minCurrentCellY, cell.frame.origin.y); } CGFloat offsetDifference = 0.0f; if (scrollDown) offsetDifference = minStoredCellY - maxCurrentCellY; else offsetDifference = maxStoredCellY - minCurrentCellY; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { maxCurrentCellY = MAX(maxCurrentCellY, CGRectGetMaxY(cell.frame)); [currentCellsWithFrames addObject:@[cell, [NSValue valueWithCGRect:cell.frame]]]; cell.frame = CGRectOffset(cell.frame, 0.0f, offsetDifference); } NSMutableArray *currentDecorationsWithFrames = [[NSMutableArray alloc] init]; for (UIView *decoration in [_collectionView visibleDecorations]) { [currentDecorationsWithFrames addObject:@[decoration, [NSValue valueWithCGRect:decoration.frame]]]; decoration.frame = CGRectOffset(decoration.frame, 0.0f, offsetDifference); } [UIView animateWithDuration:animated ? 0.3 : 0.0 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^ { for (TGModernCollectionCell *cell in storedCells) { cell.frame = CGRectOffset(cell.frame, 0.0f, -offsetDifference); } for (NSArray *desc in currentCellsWithFrames) { TGModernCollectionCell *cell = desc[0]; cell.frame = [(NSValue *)desc[1] CGRectValue]; } for (NSArray *desc in currentDecorationsWithFrames) { UIView *decoration = desc[0]; decoration.frame = [(NSValue *)desc[1] CGRectValue]; } } completion:^(__unused BOOL finished) { for (TGModernCollectionCell *cell in storedCells) { [cell removeFromSuperview]; } }]; } else { if (intent == TGModernConversationInsertItemIntentSendTextMessage) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [_inputTextPanel.maybeInputField setText:@"" animated:false]; } else if (intent == TGModernConversationInsertItemIntentSendOtherMessage) { if ([self currentReplyMessageId] != 0) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; } } _scrollingToBottom = nil; [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:false]; } [self setScrollBackMessageId:scrollBackMessageId]; if (scrollBackMessageId != 0) [self setScrollBackButtonVisible:true]; [self _updateVisibleItemIndices:nil]; } - (void)replaceItems:(NSArray *)items atIndices:(NSIndexSet *)indices { [_items replaceObjectsAtIndexes:indices withObjects:items]; [_collectionView reloadData]; [self _updateVisibleItemIndices:nil]; } - (void)deleteItemsAtIndices:(NSIndexSet *)indexSet animated:(bool)animated { [self _deleteItemsAtIndices:indexSet animated:animated animationFactor:0.7f]; } - (void)_deleteItemsAtIndices:(NSIndexSet *)indexSet animated:(bool)animated animationFactor:(CGFloat)animationFactor { NSMutableIndexSet *indexSetAnimated = [[NSMutableIndexSet alloc] initWithIndexSet:indexSet]; if (true) { CGFloat referenceContentOffset = _collectionView.contentOffset.y; CGFloat referenceContentBoundsOffset = referenceContentOffset + _collectionView.bounds.size.height; NSUInteger lastVisibleOfCurrentIndices = NSNotFound; NSUInteger farthestVisibleOfCurrentIndices = NSNotFound; int currentItemCount = (int)_items.count; for (int i = 0; i < currentItemCount; i++) { UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; CGRect itemFrame = attributes.frame; if (CGRectGetMaxY(itemFrame) > referenceContentOffset + FLT_EPSILON) { if (i != 0 || itemFrame.origin.y < referenceContentOffset - FLT_EPSILON) lastVisibleOfCurrentIndices = i; break; } } for (int i = currentItemCount - 1; i >= 0; i--) { UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; CGRect itemFrame = attributes.frame; if (itemFrame.origin.y < referenceContentBoundsOffset + FLT_EPSILON) { if (i != currentItemCount - 1 || CGRectGetMaxY(itemFrame) > referenceContentBoundsOffset - FLT_EPSILON) farthestVisibleOfCurrentIndices = i; break; } } if (lastVisibleOfCurrentIndices != NSNotFound) { bool partialReloadRequired = false; NSMutableIndexSet *indicesToRemoveWithoutAnimation = [[NSMutableIndexSet alloc] init]; NSUInteger indexCount = indexSetAnimated.count; for (NSUInteger i = 0; i < indexCount; i++) { NSUInteger currentIndex = [indexSetAnimated firstIndex]; if (currentIndex == NSNotFound || currentIndex >= lastVisibleOfCurrentIndices) break; else { [indicesToRemoveWithoutAnimation addIndex:currentIndex]; [indexSetAnimated removeIndex:currentIndex]; partialReloadRequired = true; } } if (indicesToRemoveWithoutAnimation.count != 0) { [indexSetAnimated shiftIndexesStartingAtIndex:[indicesToRemoveWithoutAnimation firstIndex] by:-indicesToRemoveWithoutAnimation.count]; [_items removeObjectsAtIndexes:indicesToRemoveWithoutAnimation]; } if (partialReloadRequired) { CGFloat previousContentHeight = _collectionLayout.collectionViewContentSize.height; [_collectionLayout prepareLayout]; CGFloat currentContentHeight = _collectionLayout.collectionViewContentSize.height; [self _beginReloadDataWithTemporaryContainer]; _collectionView.contentOffset = CGPointMake(0.0f, _collectionView.contentOffset.y + (currentContentHeight - previousContentHeight)); [self _endReloadDataWithTemporaryContainer]; [_collectionView updateRelativeBounds]; } } if (farthestVisibleOfCurrentIndices != NSNotFound) { bool partialReloadRequired = false; NSMutableIndexSet *indicesToRemoveWithoutAnimation = [[NSMutableIndexSet alloc] init]; NSUInteger indexCount = indexSetAnimated.count; for (NSUInteger i = 0; i < indexCount; i++) { NSUInteger currentIndex = [indexSetAnimated lastIndex]; if (currentIndex == NSNotFound || currentIndex <= farthestVisibleOfCurrentIndices) break; else { [indicesToRemoveWithoutAnimation addIndex:currentIndex]; [indexSetAnimated removeIndex:currentIndex]; partialReloadRequired = true; } } if (indicesToRemoveWithoutAnimation.count != 0) [_items removeObjectsAtIndexes:indicesToRemoveWithoutAnimation]; if (partialReloadRequired) { [self _beginReloadDataWithTemporaryContainer]; [self _endReloadDataWithTemporaryContainer]; [_collectionView updateRelativeBounds]; } } } if (indexSetAnimated.count != 0) { [_items removeObjectsAtIndexes:indexSetAnimated]; if (animated && indexSetAnimated.count < 100) { if (iosMajorVersion() >= 7) [TGHacks setSecondaryAnimationDurationFactor:(float)animationFactor]; else [TGHacks setAnimationDurationFactor:(float)animationFactor]; #ifndef DEBUG @try #endif { NSUInteger indexPathCount = indexSetAnimated.count; NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:indexPathCount]; NSUInteger currentIndex = [indexSetAnimated firstIndex]; for (NSUInteger i = 0; i < indexPathCount; i++) { [indexPaths addObject:[NSIndexPath indexPathForItem:currentIndex inSection:0]]; currentIndex = [indexSetAnimated indexGreaterThanIndex:currentIndex]; } [_collectionView performBatchUpdates:^ { [_collectionView deleteItemsAtIndexPaths:indexPaths]; } completion:nil beforeDecorations:nil animated:true animationFactor:(float)animationFactor]; } #ifndef DEBUG @catch (NSException *e) { TGLog(@"%@", e); [self _resetCollectionView]; } #endif [TGHacks setSecondaryAnimationDurationFactor:1.0f]; [TGHacks setAnimationDurationFactor:1.0f]; [_collectionView updateRelativeBounds]; } else { [self _beginReloadDataWithTemporaryContainer]; [self _endReloadDataWithTemporaryContainer]; [_collectionView updateRelativeBounds]; } } [self _updateVisibleItemIndices:nil]; } - (void)moveItems:(NSArray *)moveIndexPairs { NSMutableArray *movingItems = [[NSMutableArray alloc] init]; for (NSArray *pair in moveIndexPairs) { id item = _items[[pair[0] intValue]]; [movingItems addObject:item]; [_items removeObjectAtIndex:[pair[0] intValue]]; } int index = (int)movingItems.count; for (NSArray *pair in moveIndexPairs.reverseObjectEnumerator) { index--; [_items insertObject:movingItems[index] atIndex:[pair[1] intValue]]; } #ifndef DEBUG @try #endif { [_collectionView performBatchUpdates:^ { for (NSArray *pair in moveIndexPairs) { [_collectionView moveItemAtIndexPath:[NSIndexPath indexPathForItem:[pair[0] intValue] inSection:0] toIndexPath:[NSIndexPath indexPathForItem:[pair[1] intValue] inSection:0]]; } } completion:nil]; } #ifndef DEBUG @catch (NSException *e) { TGLog(@"%@", e); [self _resetCollectionView]; } #endif [_collectionView updateRelativeBounds]; } - (void)insertItems:(NSArray *)itemsArray atIndices:(NSIndexSet *)indexSet animated:(bool)animated intent:(TGModernConversationInsertItemIntent)intent { [self insertItems:itemsArray atIndices:indexSet animated:animated intent:intent removeAtIndices:nil]; } - (void)insertItems:(NSArray *)itemsArray atIndices:(NSIndexSet *)indexSet animated:(bool)animated intent:(TGModernConversationInsertItemIntent)intent removeAtIndices:(NSIndexSet *)removeIndexSet { if (removeIndexSet.count != 0) { [self _deleteItemsAtIndices:removeIndexSet animated:animated animationFactor:0.7f]; } bool scrollToBottom = [_scrollingToBottom boolValue]; if (indexSet.count != itemsArray.count) { TGLog(@"***** %s:%s: indices.count != insertedItems.count", __FILE__, __PRETTY_FUNCTION__); return; } NSMutableArray *insertItemsAnimated = [[NSMutableArray alloc] initWithArray:itemsArray]; NSMutableIndexSet *insertIndicesAnimated = [[NSMutableIndexSet alloc] initWithIndexSet:indexSet]; if (true) { CGFloat referenceContentOffset = _collectionView.contentOffset.y + _collectionView.contentInset.top; NSUInteger lastVisibleOfCurrentIndices = NSNotFound; NSUInteger currentItemCount = _items.count; for (NSUInteger i = 0; i < currentItemCount; i++) { UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; CGRect itemFrame = attributes.frame; if (CGRectGetMaxY(itemFrame) > referenceContentOffset + FLT_EPSILON) { if (i != 0 || itemFrame.origin.y < referenceContentOffset - FLT_EPSILON) lastVisibleOfCurrentIndices = i; break; } } bool partialReloadRequired = false; if (lastVisibleOfCurrentIndices != NSNotFound || intent == TGModernConversationInsertItemIntentLoadMoreMessagesBelow) { bool hadIncomingUnread = false; NSUInteger modifiedLastVisibleOfCurrentIndices = lastVisibleOfCurrentIndices; NSUInteger insertedItems = 0; for (NSUInteger i = 0; i < insertItemsAnimated.count; i++) { NSUInteger currentIndex = [insertIndicesAnimated firstIndex]; if ((intent != TGModernConversationInsertItemIntentLoadMoreMessagesBelow && currentIndex > modifiedLastVisibleOfCurrentIndices) || currentIndex == NSNotFound) break; else { [_items insertObject:insertItemsAnimated[i] atIndex:currentIndex]; insertedItems++; if (intent != TGModernConversationInsertItemIntentLoadMoreMessagesBelow) { TGMessageModernConversationItem *messageItem = insertItemsAnimated[i]; if (!messageItem->_message.outgoing && messageItem->_message.unread) hadIncomingUnread = true; } [insertIndicesAnimated removeIndex:currentIndex]; if (modifiedLastVisibleOfCurrentIndices != NSNotFound) modifiedLastVisibleOfCurrentIndices++; partialReloadRequired = true; } } if (insertedItems != 0) [insertItemsAnimated removeObjectsInRange:NSMakeRange(0, insertedItems)]; if (hadIncomingUnread) [self setHasUnseenMessagesBelow:true]; } if (partialReloadRequired) { CGFloat previousContentHeight = _collectionLayout.collectionViewContentSize.height; [_collectionLayout prepareLayout]; CGFloat currentContentHeight = _collectionLayout.collectionViewContentSize.height; [self _beginReloadDataWithTemporaryContainer]; if (intent == TGModernConversationInsertItemIntentLoadMoreMessagesBelow) { _collectionView.contentOffset = CGPointMake(0.0f, _collectionView.contentOffset.y + (currentContentHeight - previousContentHeight)); } else { _collectionView.contentOffset = CGPointMake(0.0f, _collectionView.contentOffset.y + (currentContentHeight - previousContentHeight)); } [self _endReloadDataWithTemporaryContainer]; } } if (insertIndicesAnimated.count != 0) { [_items insertObjects:insertItemsAnimated atIndexes:insertIndicesAnimated]; if (animated) { if (iosMajorVersion() >= 7) [TGHacks setSecondaryAnimationDurationFactor:0.7f]; else [TGHacks setAnimationDurationFactor:0.7f]; #ifndef DEBUG @try #endif { NSUInteger indexPathCount = insertIndicesAnimated.count; NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:indexPathCount]; NSUInteger currentIndex = [insertIndicesAnimated firstIndex]; for (NSUInteger i = 0; i < indexPathCount; i++) { [indexPaths addObject:[NSIndexPath indexPathForItem:currentIndex inSection:0]]; currentIndex = [insertIndicesAnimated indexGreaterThanIndex:currentIndex]; } [_collectionView performBatchUpdates:^ { [_collectionView insertItemsAtIndexPaths:indexPaths]; } completion:nil beforeDecorations:^ { if (intent == TGModernConversationInsertItemIntentSendTextMessage || intent == TGModernConversationInsertItemIntentSendOtherMessage) { if (intent == TGModernConversationInsertItemIntentSendTextMessage) { _inputTextPanel.maybeInputField.oneTimeLongAnimation = true; [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:_inputTextPanel.maybeInputField.text.length != 0]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:_inputTextPanel.maybeInputField.text.length != 0]; [_inputTextPanel.maybeInputField setText:@""]; } else if (intent == TGModernConversationInsertItemIntentSendOtherMessage) { if ([self currentReplyMessageId] != 0) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; } } if (_collectionView.contentOffset.y > -_collectionView.contentInset.top) { [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true]; _scrollingToBottom = @true; } //[_collectionView scrollRectToVisible:CGRectMake(0.0f, 0.0f, 1.0f, 1.0f) animated:true]; } } animated:true animationFactor:0.7f]; } #ifndef DEBUG @catch (NSException *e) { TGLog(@"%@", e); [self _resetCollectionView]; } #endif [TGHacks setSecondaryAnimationDurationFactor:1.0f]; [TGHacks setAnimationDurationFactor:1.0f]; [_collectionView updateRelativeBounds]; } else { [self _beginReloadDataWithTemporaryContainer]; [self _endReloadDataWithTemporaryContainer]; if (intent == TGModernConversationInsertItemIntentLoadMoreMessagesBelow) { } [_collectionView updateRelativeBounds]; if (intent == TGModernConversationInsertItemIntentSendTextMessage || intent == TGModernConversationInsertItemIntentSendOtherMessage) { if (intent == TGModernConversationInsertItemIntentSendTextMessage) { _inputTextPanel.maybeInputField.oneTimeLongAnimation = true; [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:_inputTextPanel.maybeInputField.text.length != 0]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:_inputTextPanel.maybeInputField.text.length != 0]; [_inputTextPanel.maybeInputField setText:@"" animated:true]; } else if (intent == TGModernConversationInsertItemIntentSendOtherMessage) { if ([self currentReplyMessageId] != 0) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; } } [_collectionView scrollRectToVisible:CGRectMake(0.0f, 0.0f, 1.0f, 1.0f) animated:true]; } } } else if (intent == TGModernConversationInsertItemIntentSendTextMessage || intent == TGModernConversationInsertItemIntentSendOtherMessage) { if (intent == TGModernConversationInsertItemIntentSendTextMessage) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [_inputTextPanel.maybeInputField setText:@"" animated:false]; } else if (intent == TGModernConversationInsertItemIntentSendOtherMessage) { if ([self currentReplyMessageId] != 0) { [self setPrimaryExtendedPanel:nil animated:true skipHeightAnimation:false]; [self setSecondaryExtendedPanel:nil animated:true skipHeightAnimation:false]; } } if (_collectionView.contentOffset.y > -_collectionView.contentInset.top) { [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true]; _scrollingToBottom = @true; } } if (_enableUnloadHistoryRequests && (NSInteger)_items.count >= TGModernConversationControllerUnloadHistoryLimit + TGModernConversationControllerUnloadHistoryThreshold) [self _maybeUnloadHistory]; if (scrollToBottom) { if (_collectionView.contentOffset.y > -_collectionView.contentInset.top) { [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true]; _scrollingToBottom = @true; } } if ([self currentReplyMessageId] == 0 && intent == TGModernConversationInsertItemIntentGeneric) { for (TGMessageModernConversationItem *item in itemsArray) { if (item->_message.forceReply) { [self setReplyMessage:item->_message animated:true]; break; } } } [self _updateVisibleItemIndices:nil]; } - (void)updateItemAtIndex:(NSUInteger)index toItem:(TGModernConversationItem *)updatedItem delayAvailability:(bool)delayAvailability { [self updateItemAtIndex:index toItem:updatedItem delayAvailability:delayAvailability animated:true]; } - (void)updateItemAtIndex:(NSUInteger)index toItem:(TGModernConversationItem *)updatedItem delayAvailability:(bool)delayAvailability animated:(bool)animated { CGFloat containerWidth = _collectionView == nil ? _view.frame.size.width : _collectionView.frame.size.width; bool sizeChanged = false; CGSize lastSize = [(TGMessageModernConversationItem *)_items[index] sizeForContainerSize:CGSizeMake(containerWidth, CGFLOAT_MAX)]; [_items[index] updateToItem:updatedItem viewStorage:_viewStorage sizeChanged:&sizeChanged delayAvailability:delayAvailability]; CGSize updatedSize = lastSize; if (sizeChanged) { updatedSize = [(TGMessageModernConversationItem *)_items[index] sizeForContainerSize:CGSizeMake(containerWidth, CGFLOAT_MAX)]; } if (sizeChanged && ABS(lastSize.height - updatedSize.height) != 0) { if (_collectionView.isDecelerating) { [_collectionLayout invalidateLayout]; [_collectionView layoutSubviews]; } else { std::vector decorationAttributes; NSArray *layoutAttributes = [_collectionLayout layoutAttributesForItems:_items containerWidth:containerWidth maxHeight:FLT_MAX decorationViewAttributes:&decorationAttributes contentHeight:NULL]; if (animated) { [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { for (NSIndexPath *indexPath in _collectionView.indexPathsForVisibleItems) { TGModernCollectionCell *cell = (TGModernCollectionCell *)[_collectionView cellForItemAtIndexPath:indexPath]; TGMessageModernConversationItem *cellItem = cell.boundItem; if (cellItem != nil) { UICollectionViewLayoutAttributes *attributes = layoutAttributes[indexPath.row]; cell.frame = attributes.frame; } } } completion:^(BOOL finished) { if (finished) [_collectionLayout invalidateLayout]; }]; } else { [_collectionLayout invalidateLayout]; } [_collectionView updateRelativeBounds]; } } } - (void)updateItemProgressAtIndex:(NSUInteger)index toProgress:(CGFloat)progress animated:(bool)animated { if (index >= _items.count) return; [_items[index] updateProgress:(float)progress viewStorage:_viewStorage animated:animated]; } - (void)imageDataInvalidated:(NSString *)imageUrl { if (_collectionView != nil) { for (TGModernCollectionCell *cell in [_collectionView visibleCells]) { [(TGMessageModernConversationItem *)cell.boundItem imageDataInvalidated:imageUrl]; } } else { for (TGMessageModernConversationItem *messageItem in _items) { [messageItem imageDataInvalidated:imageUrl]; } } } - (void)updateCheckedMessages { if (_editingMode) { for (TGModernCollectionCell *cell in _collectionView.visibleCells) { [(TGMessageModernConversationItem *)cell.boundItem updateEditingState:_viewStorage animationDelay:0.0]; } [self _updateEditingPanel]; } } - (void)updateMessageAttributes:(int32_t)messageId { for (TGMessageModernConversationItem *messageItem in _items) { if (messageItem->_message.mid == messageId) { [messageItem updateMessageAttributes]; break; } } } - (void)setHasUnseenMessagesBelow:(bool)hasUnseenMessagesBelow { if (!self.isViewLoaded) return; _hasUnseenMessagesBelow = hasUnseenMessagesBelow; [self setScrollBackButtonVisible:_hasUnseenMessagesBelow || _scrollBackMessageId != 0]; } - (void)setUnreadMessageRangeIfAppropriate:(TGMessageRange)unreadMessageRange { if (!TGMessageRangeEquals(_collectionView.unreadMessageRange, unreadMessageRange)) { _collectionView.unreadMessageRange = unreadMessageRange; [_collectionView reloadData]; [_collectionView layoutSubviews]; int32_t minMessageId = INT32_MAX; for (TGMessageModernConversationItem *item in _items) { if (item->_message.mid >= unreadMessageRange.firstMessageId) { if (item->_message.mid < minMessageId || minMessageId == INT32_MIN) { minMessageId = item->_message.mid; } } } if (minMessageId != INT32_MAX) { CGFloat contentOffset = [self contentOffsetForMessageId:minMessageId scrollPosition:TGInitialScrollPositionTop initial:false additionalOffset:0.0f]; [_collectionView setContentOffset:CGPointMake(0.0f, contentOffset) animated:false]; } } } - (void)setScrollBackMessageId:(int32_t)scrollBackMessageId { if (!self.isViewLoaded) return; _scrollBackMessageId = scrollBackMessageId; } - (void)setScrollBackButtonVisible:(bool)scrollBackButtonVisible { if (scrollBackButtonVisible) { if (_unseenMessagesButton == nil) { UIImage *image = [UIImage imageNamed:@"ModernConversationUnseenMessagesButton.png"]; _unseenMessagesButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, image.size.width, image.size.height)]; [_unseenMessagesButton setImage:image forState:UIControlStateNormal]; _unseenMessagesButton.adjustsImageWhenHighlighted = false; [_unseenMessagesButton addTarget:self action:@selector(unseenMessagesButtonPressed) forControlEvents:UIControlEventTouchUpInside]; } if (_unseenMessagesButton.superview == nil) { [_view insertSubview:_unseenMessagesButton aboveSubview:_collectionView]; [self _updateUnseenMessagesButton]; } } else if (_unseenMessagesButton != nil) { [_unseenMessagesButton removeFromSuperview]; } } - (void)_updateUnseenMessagesButton { if (_unseenMessagesButton.superview != nil) { CGSize collectionViewSize = _view.bounds.size; CGSize buttonSize = _unseenMessagesButton.frame.size; _unseenMessagesButton.frame = CGRectMake(collectionViewSize.width - buttonSize.width - 6, collectionViewSize.height - buttonSize.height - _collectionView.contentInset.top - 6, buttonSize.width, buttonSize.height); } } - (void)_updateEditingPanel { if ([_currentInputPanel isKindOfClass:[TGModernConversationEditingPanel class]]) { TGModernConversationEditingPanel *editingPanel = (TGModernConversationEditingPanel *)_currentInputPanel; [editingPanel setActionsEnabled:[_companion checkedMessageCount] != 0]; [editingPanel setDeleteEnabled:[self canDeleteSelectedMessages]]; } } - (void)_beginReloadDataWithTemporaryContainer { _itemsBoundToTemporaryContainer = [[NSMutableArray alloc] init]; UIView *tempContainer = [[UIView alloc] init]; for (NSIndexPath *indexPath in [_collectionView indexPathsForVisibleItems]) { TGModernCollectionCell *cell = (TGModernCollectionCell *)[_collectionView cellForItemAtIndexPath:indexPath]; TGModernConversationItem *item = [cell boundItem]; if (item != nil) { [item temporaryMoveToView:tempContainer]; [_itemsBoundToTemporaryContainer addObject:item]; #if TGModernConversationControllerLogCellOperations TGLog(@"(store item %d)", indexPath.item); #endif } } _disableItemBinding = true; [_collectionView reloadData]; } - (void)_endReloadDataWithTemporaryContainer { [_collectionView updateVisibleItemsNow]; [_collectionView layoutIfNeeded]; _disableItemBinding = false; for (NSIndexPath *indexPath in _collectionView.indexPathsForVisibleItems) { TGModernCollectionCell *cell = (TGModernCollectionCell *)[_collectionView cellForItemAtIndexPath:indexPath]; if (cell.boundItem == nil && indexPath.row < (NSInteger)_items.count) { [self _bindItem:_items[indexPath.row] toCell:cell atIndexPath:indexPath]; } } for (TGModernConversationItem *item in _itemsBoundToTemporaryContainer) { [item unbindCell:_viewStorage]; } _itemsBoundToTemporaryContainer = nil; } - (void)updateItems:(NSArray *)updatedItems atIndices:(NSArray *)indices { if (indices.count == 0) return; if (indices.count != updatedItems.count) { TGLog(@"***** %s:%s: indices.count != updatedItems", __FILE__, __PRETTY_FUNCTION__); return; } int index = -1; bool sizeChanged = false; for (NSNumber *nIndex in indices) { index++; [(TGModernConversationItem *)_items[[nIndex intValue]] updateToItem:updatedItems[index] viewStorage:_viewStorage sizeChanged:&sizeChanged delayAvailability:false]; } } - (void)scrollToMessage:(int32_t)messageId sourceMessageId:(int32_t)sourceMessageId animated:(bool)animated { TGMessageModernConversationItem *selectedItem = nil; for (TGMessageModernConversationItem *messageItem in _items) { if (messageItem->_message.mid == messageId) { selectedItem = messageItem; break; } } if (selectedItem != nil) { bool foundCell = false; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { if (((TGMessageModernConversationItem *)cell.boundItem)->_message.mid == messageId) { foundCell = true; break; } } if (animated) { if (foundCell) { [selectedItem setTemporaryHighlighted:true viewStorage:_viewStorage]; TGDispatchAfter(0.6, dispatch_get_main_queue(), ^ { [selectedItem setTemporaryHighlighted:false viewStorage:_viewStorage]; }); } else _temporaryHighlightMessageIdUponDisplay = messageId; } CGFloat contentOffset = [self contentOffsetForMessageId:messageId scrollPosition:TGInitialScrollPositionCenter initial:false additionalOffset:0.0f]; if (ABS(contentOffset - _collectionView.contentOffset.y) > FLT_EPSILON) { _canCheckScrollBackMessageId = false; [self setScrollBackMessageId:sourceMessageId]; [_collectionView setContentOffset:CGPointMake(0.0f, contentOffset) animated:animated]; } else { [self setScrollBackMessageId:0]; [self setScrollBackButtonVisible:false]; } } } - (int64_t)peerId { return ((TGGenericModernConversationCompanion *)_companion).conversationId; } - (int32_t)convertMessageId:(int32_t)messageId fromPeerId:(int64_t)peerId { if (peerId == [self peerId]) { return messageId; } else { return messageId + migratedMessageIdOffset; } } - (int32_t)convertMessageId:(int32_t)messageId toPeerId:(int64_t)peerId { if (peerId == [self peerId]) { return messageId; } else { return messageId - migratedMessageIdOffset; } } - (void)openMediaFromMessage:(int32_t)messageId instant:(bool)instant { TGMessageModernConversationItem *mediaMessageItem = nil; TGModernCollectionCell *mediaItemCell = nil; int index = -1; for (TGMessageModernConversationItem *messageItem in _items) { index++; if (messageItem->_message.mid == messageId) { TGModernCollectionCell *cell = (TGModernCollectionCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; if (messageItem->_mediaAvailabilityStatus && cell != nil) { mediaMessageItem = messageItem; mediaItemCell = cell; } break; } } if (mediaMessageItem != nil && index >= 0) { TGUser *author = nil; if (mediaMessageItem->_message.fromUid == 0) { author = [[TGUser alloc] init]; author.uid = 0; author.firstName = _titleView.title; } else if (!TGPeerIdIsChannel(mediaMessageItem->_message.fromUid)) { author = [TGDatabaseInstance() loadUser:mediaMessageItem->_message.outgoing ? TGTelegraphInstance.clientUserId : (int32_t)mediaMessageItem->_message.fromUid]; } else { author = [[TGUser alloc] init]; author.uid = 0; author.firstName = _titleView.title; } if (author == nil) return; bool isGallery = false; bool isAvatar = false; TGImageInfo *avatarImageInfo = nil; TGWebPageMediaAttachment *webPage = nil; int32_t webPageMessageId = 0; bool foundMedia = false; for (TGMediaAttachment *attachment in mediaMessageItem->_message.mediaAttachments) { switch (attachment.type) { case TGVideoMediaAttachmentType: case TGImageMediaAttachmentType: { foundMedia = true; isGallery = true; break; } case TGWebPageMediaAttachmentType: { webPage = ((TGWebPageMediaAttachment *)attachment); foundMedia = true; webPageMessageId = mediaMessageItem->_message.mid; break; } case TGActionMediaAttachmentType: { TGActionMediaAttachment *actionAttachment = (TGActionMediaAttachment *)attachment; switch (actionAttachment.actionType) { case TGMessageActionChatEditPhoto: { foundMedia = true; TGImageMediaAttachment *photo = actionAttachment.actionData[@"photo"]; isAvatar = true; avatarImageInfo = photo.imageInfo; break; } default: break; } break; } case TGLocationMediaAttachmentType: { int64_t peerId = mediaMessageItem->_message.fromUid; if (mediaMessageItem->_message.forwardPeerId != 0) { peerId = mediaMessageItem->_message.forwardPeerId; } TGLocationMediaAttachment *mapAttachment = (TGLocationMediaAttachment *)attachment; __weak TGModernConversationController *weakSelf = self; id peer = nil; if (TGPeerIdIsChannel(peerId)) { peer = [TGDatabaseInstance() loadChannels:@[@(peerId)]][@(peerId)]; } else { peer = [TGDatabaseInstance() loadUser:(int32_t)peerId]; } TGLocationViewController *controller = [[TGLocationViewController alloc] initWithCoordinate:CLLocationCoordinate2DMake(mapAttachment.latitude, mapAttachment.longitude) venue:mapAttachment.venue peer:peer]; controller.calloutPressed = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([peer isKindOfClass:[TGConversation class]]) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf.navigationController popViewControllerAnimated:true]; } } else if ([peer isKindOfClass:[TGUser class]]) { [[TGInterfaceManager instance] navigateToProfileOfUser:((TGUser *)peer).uid]; } } }; if ([_companion allowMessageForwarding]) { controller.forwardPressed = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf forwardMessages:@[@(mediaMessageItem->_message.mid)] fastForward:false]; } }; } if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { [self.navigationController pushViewController:controller animated:true]; } else { TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[controller]]; navigationController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:navigationController animated:true completion:nil]; } break; } case TGDocumentMediaAttachmentType: { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; bool isVoice = false; for (id attribute in documentAttachment.attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) { TGDocumentAttributeAudio *audio = attribute; if (audio.isVoice) { isVoice = true; } break; } } if (isVoice) { break; } if ([[[documentAttachment.fileName pathExtension] lowercaseString] isEqualToString:@"strings"]) { [[[TGActionSheet alloc] initWithTitle:nil actions:@[ [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.ApplyLocalization") action:@"applyLocalization"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.OpenFile") action:@"open"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel], ] actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"applyLocalization"]) { NSBundle *referenceBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"]]; NSDictionary *referenceDict = [NSDictionary dictionaryWithContentsOfFile:[referenceBundle pathForResource:@"Localizable" ofType:@"strings"]]; NSDictionary *localizationDict = [NSDictionary dictionaryWithContentsOfFile:[_companion fileUrlForDocumentMedia:documentAttachment].path]; __block bool valid = true; NSMutableArray *missingKeys = [[NSMutableArray alloc] init]; NSMutableArray *invalidFormatKeys = [[NSMutableArray alloc] init]; NSString *invalidFileString = nil; if (localizationDict != nil && referenceDict != nil) { [referenceDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *sourceValue, __unused BOOL *stop) { NSString *targetValue = localizationDict[key]; if (targetValue == nil) { [missingKeys addObject:key]; } else { for (int i = 0; i < 2; i++) { NSString *firstValue = i == 0 ? sourceValue : targetValue; NSString *secondValue = i == 0 ? targetValue : sourceValue; NSRange firstRange = NSMakeRange(0, firstValue.length); NSRange secondRange = NSMakeRange(0, secondValue.length); while (firstRange.length != 0) { NSRange range = [firstValue rangeOfString:@"%" options:0 range:firstRange]; if (range.location == NSNotFound || range.location == firstValue.length - 1) break; else { firstRange.location = range.location + range.length; firstRange.length = firstValue.length - firstRange.location; NSString *findPositionalString = nil; NSString *findFreeString = nil; unichar c = [firstValue characterAtIndex:range.location + 1]; if (c == 'd' || c == 'f') findPositionalString = [[NSString alloc] initWithFormat:@"%%%c", (char)c]; else if (c >= '0' && c <= '9') { if (range.location + 3 < firstValue.length) { if ([firstValue characterAtIndex:range.location + 2] == '$') { unichar formatChar = [firstValue characterAtIndex:range.location + 3]; findFreeString = [[NSString alloc] initWithFormat:@"%%%c$%c", (char)c, (char)formatChar]; } } } if (findPositionalString != nil) { NSRange foundRange = [secondValue rangeOfString:findPositionalString options:0 range:secondRange]; if (foundRange.location != NSNotFound) { secondRange.location = foundRange.location + foundRange.length; secondRange.length = secondValue.length - secondRange.location; } else { valid = false; [invalidFormatKeys addObject:key]; break; } } else if (findFreeString != nil) { if ([secondValue rangeOfString:findFreeString].location == NSNotFound) { valid = false; [invalidFormatKeys addObject:key]; break; } } } } } } }]; } else { valid = false; invalidFileString = @"invalid localization file format"; } if (valid) { NSMutableString *missingKeysString = [[NSMutableString alloc] init]; static const int maxKeys = 5; for (int i = 0; i < maxKeys && i < (int)missingKeys.count; i++) { if (missingKeysString.length != 0) [missingKeysString appendString:@", "]; [missingKeysString appendString:missingKeys[i]]; if (i == maxKeys - 1 && maxKeys < (int)missingKeys.count) [missingKeysString appendFormat:@" and %d more", (int)(missingKeys.count - maxKeys)]; } if (missingKeysString.length != 0) { TGAlertView *alertView = [[TGAlertView alloc] initWithTitle:nil message:[[NSString alloc] initWithFormat:@"Localization file is valid, but the following keys are missing: %@", missingKeysString] delegate:nil cancelButtonTitle:TGLocalized(@"Common.OK") otherButtonTitles:nil]; [alertView show]; } [controller.companion controllerWantsToApplyLocalization:[_companion fileUrlForDocumentMedia:documentAttachment].path]; } else { NSString *reasonString = nil; if (invalidFileString.length != 0) reasonString = invalidFileString; else if (invalidFormatKeys.count != 0) { NSMutableString *invalidFormatKeysString = [[NSMutableString alloc] init]; static const int maxKeys = 5; for (int i = 0; i < maxKeys && i < (int)invalidFormatKeys.count; i++) { if (invalidFormatKeysString.length != 0) [invalidFormatKeysString appendString:@", "]; [invalidFormatKeysString appendString:invalidFormatKeys[i]]; if (i == maxKeys - 1 && maxKeys < (int)invalidFormatKeys.count) [invalidFormatKeysString appendFormat:@" and %d more", (int)(invalidFormatKeys.count - maxKeys)]; } reasonString = [[NSString alloc] initWithFormat:@"invalid value format for keys %@", invalidFormatKeysString]; } TGAlertView *alertView = [[TGAlertView alloc] initWithTitle:nil message:[[NSString alloc] initWithFormat:@"Invalid localization file: %@", reasonString] delegate:nil cancelButtonTitle:TGLocalized(@"Common.OK") otherButtonTitles:nil]; [alertView show]; } } else if ([action isEqualToString:@"open"]) { TGDocumentController *documentController = [[TGDocumentController alloc] initWithURL:[controller.companion fileUrlForDocumentMedia:documentAttachment] messageId:mediaMessageItem->_message.mid]; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) [controller.navigationController pushViewController:documentController animated:true]; else { if (iosMajorVersion() >= 8) { documentController.modalPresentationStyle = UIModalPresentationFormSheet; [controller presentViewController:documentController animated:false completion:nil]; } else { TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[documentController]]; navigationController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; [controller presentViewController:navigationController animated:true completion:nil]; } } [controller.companion updateMediaAccessTimeForMessageId:messageId]; } } target:self] showInView:_view]; break; } TGDocumentController *documentController = [[TGDocumentController alloc] initWithURL:[_companion fileUrlForDocumentMedia:documentAttachment] messageId:mediaMessageItem->_message.mid]; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) [self.navigationController pushViewController:documentController animated:true]; else { if (iosMajorVersion() >= 8) { documentController.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:documentController animated:false completion:nil]; } else { TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[documentController]]; navigationController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:navigationController animated:true completion:nil]; } } [_companion updateMediaAccessTimeForMessageId:messageId]; break; } case TGAudioMediaAttachmentType: { break; } default: break; } if (foundMedia) break; } if (!foundMedia) return; [self stopInlineMediaIfPlaying]; int64_t peerId = mediaMessageItem->_message.cid; TGModernGalleryController *modernGallery = [[TGModernGalleryController alloc] init]; if (webPage != nil) { modernGallery.model = [[TGExternalGalleryModel alloc] initWithWebPage:webPage peerId:peerId messageId:messageId]; } else if (isGallery) { if (mediaMessageItem->_message.messageLifetime > 0 && mediaMessageItem->_message.messageLifetime <= 60 && mediaMessageItem->_message.layer >= 17) { modernGallery.model = [[TGSecretPeerMediaGalleryModel alloc] initWithPeerId:((TGGenericModernConversationCompanion *)_companion).conversationId messageId:mediaMessageItem->_message.mid]; } else if (!_companion.allowMessageForwarding) { modernGallery.model = [[TGSecretInfiniteLifetimePeerMediaGalleryModel alloc] initWithPeerId:((TGGenericModernConversationCompanion *)_companion).conversationId atMessageId:mediaMessageItem->_message.mid allowActions:_companion.allowMessageForwarding important:TGMessageSortKeySpace(mediaMessageItem->_message.sortKey) == TGMessageSpaceImportant]; } else { if (mediaMessageItem->_message.cid == [self peerId]) { modernGallery.model = [[TGGenericPeerMediaGalleryModel alloc] initWithPeerId:[self peerId] atMessageId:mediaMessageItem->_message.mid allowActions:_companion.allowMessageForwarding important:TGMessageSortKeySpace(mediaMessageItem->_message.sortKey) == TGMessageSpaceImportant]; } else { modernGallery.model = [[TGGenericPeerMediaGalleryModel alloc] initWithPeerId:mediaMessageItem->_message.cid atMessageId:[self convertMessageId:mediaMessageItem->_message.mid toPeerId:peerId] allowActions:_companion.allowMessageForwarding important:TGMessageSortKeySpace(mediaMessageItem->_message.sortKey) == TGMessageSpaceImportant]; ((TGGenericPeerMediaGalleryModel *)modernGallery.model).attachedPeerId = peerId; } } } else if (isAvatar) { NSString *legacyThumbnailUrl = [avatarImageInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; NSString *legacyUrl = [avatarImageInfo imageUrlForLargestSize:NULL]; modernGallery.model = [[TGGroupAvatarGalleryModel alloc] initWithMessageId:mediaMessageItem->_message.mid legacyThumbnailUrl:legacyThumbnailUrl legacyUrl:legacyUrl imageSize:CGSizeMake(640.0f, 640.0f)]; } __weak TGModernConversationController *weakSelf = self; modernGallery.itemFocused = ^(id item) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([item conformsToProtocol:@protocol(TGGenericPeerGalleryItem)]) { id concreteItem = (id)item; int32_t messageId = [strongSelf convertMessageId:[concreteItem messageId] fromPeerId:peerId]; strongSelf.companion.mediaHiddenMessageId = messageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { [(TGMessageModernConversationItem *)[cell boundItem] updateMediaVisibility]; } } else if ([item isKindOfClass:[TGGroupAvatarGalleryItem class]]) { int32_t messageId = ((TGGroupAvatarGalleryItem *)item).messageId; strongSelf.companion.mediaHiddenMessageId = messageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { [(TGMessageModernConversationItem *)[cell boundItem] updateMediaVisibility]; } } else { int32_t messageId = webPageMessageId; strongSelf.companion.mediaHiddenMessageId = messageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { [(TGMessageModernConversationItem *)[cell boundItem] updateMediaVisibility]; } } } }; modernGallery.finishedTransitionIn = ^(__unused id item, TGModernGalleryItemView *itemView) { if ([itemView isKindOfClass:[TGModernGalleryVideoItemView class]]) { [((TGModernGalleryVideoItemView *)itemView) play]; } }; modernGallery.beginTransitionIn = ^UIView *(id item, TGModernGalleryItemView *itemView) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([item conformsToProtocol:@protocol(TGGenericPeerGalleryItem)]) { if ([itemView isKindOfClass:[TGModernGalleryVideoItemView class]]) { [((TGModernGalleryVideoItemView *)itemView) hidePlayButton]; } id concreteItem = (id)item; int32_t messageId = [strongSelf convertMessageId:[concreteItem messageId] fromPeerId:peerId]; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { TGMessageModernConversationItem *cellItem = [cell boundItem]; if (cellItem != nil && cellItem->_message.mid == messageId) { return [cellItem referenceViewForImageTransition]; } } } else if ([item isKindOfClass:[TGGroupAvatarGalleryItem class]]) { int32_t messageId = ((TGGroupAvatarGalleryItem *)item).messageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { TGMessageModernConversationItem *cellItem = [cell boundItem]; if (cellItem != nil && cellItem->_message.mid == messageId) { return [cellItem referenceViewForImageTransition]; } } } else { int32_t messageId = webPageMessageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { TGMessageModernConversationItem *cellItem = [cell boundItem]; if (cellItem != nil && cellItem->_message.mid == messageId) { return [cellItem referenceViewForImageTransition]; } } } } return nil; }; modernGallery.beginTransitionOut = ^UIView *(id item, TGModernGalleryItemView *itemView) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf _dismissBannersForCurrentConversation]; if ([itemView isKindOfClass:[TGModernGalleryVideoItemView class]]) [((TGModernGalleryVideoItemView *)itemView) stop]; if ([item conformsToProtocol:@protocol(TGGenericPeerGalleryItem)]) { id concreteItem = (id)item; int32_t messageId = [strongSelf convertMessageId:[concreteItem messageId] fromPeerId:peerId]; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { TGMessageModernConversationItem *cellItem = [cell boundItem]; if (cellItem != nil && cellItem->_message.mid == messageId) { return [cellItem referenceViewForImageTransition]; } } } else if ([item isKindOfClass:[TGGroupAvatarGalleryItem class]]) { int32_t messageId = ((TGGroupAvatarGalleryItem *)item).messageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { TGMessageModernConversationItem *cellItem = [cell boundItem]; if (cellItem != nil && cellItem->_message.mid == messageId) { return [cellItem referenceViewForImageTransition]; } } } else { int32_t messageId = webPageMessageId; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { TGMessageModernConversationItem *cellItem = [cell boundItem]; if (cellItem != nil && cellItem->_message.mid == messageId) { return [cellItem referenceViewForImageTransition]; } } } } return nil; }; modernGallery.completedTransitionOut = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { strongSelf.companion.mediaHiddenMessageId = 0; for (TGModernCollectionCell *cell in strongSelf->_collectionView.visibleCells) { [(TGMessageModernConversationItem *)[cell boundItem] updateMediaVisibility]; } } }; modernGallery.animateTransition = !instant; modernGallery.showInterface = !instant; [self closeExistingMediaGallery]; TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithParentController:self contentController:modernGallery keepKeyboard:instant]; if (instant) { controllerWindow.windowLevel = 10000000000.0f; } controllerWindow.hidden = false; } } - (bool)playNextUnseenIncomingAudio { for (TGMessageModernConversationItem *item in _items.reverseObjectEnumerator) { if (item->_message.outgoing) continue; bool isVoice = false; for (id attachment in item->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGAudioMediaAttachment class]]) { isVoice = true; break; } else if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { for (id attribute in ((TGDocumentMediaAttachment *)attachment).attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) { if (((TGDocumentAttributeAudio *)attribute).isVoice) { isVoice = true; } } } break; } } if (isVoice) { if (TGPeerIdIsSecretChat(item->_message.cid)) { if (![_companion _isSecretMessageViewed:item->_message.mid]) { _companion.viewContext.playAudioMessageId(item->_message.mid); return true; } } else { if (item->_message.contentProperties[@"contentsRead"] == nil) { _companion.viewContext.playAudioMessageId(item->_message.mid); return true; } } } } return false; } /*- (void)audioPlayerDidFinish { int32_t currentStreamingAudioMessageId = _currentStreamingAudioMessageId; [self stopInlineMediaIfPlaying]; if ((true || _streamAudioItems) && currentStreamingAudioMessageId != 0) { int32_t lastIncomingAudioMessageId = 0; TGAudioMediaAttachment *lastIncomingAudioMessageMedia = nil; for (TGMessageModernConversationItem *item in _items) { if (item->_message.outgoing) continue; if (item->_message.mid <= currentStreamingAudioMessageId) break; for (id attachment in item->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGAudioMediaAttachment class]]) { lastIncomingAudioMessageId = item->_message.mid; if (item->_mediaAvailabilityStatus) lastIncomingAudioMessageMedia = attachment; break; } else if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { for (id attribute in ((TGDocumentMediaAttachment *)attachment).attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) { if (((TGDocumentAttributeAudio *)attribute).isVoice) { lastIncomingAudioMessageId = item->_message.mid; if (item->_mediaAvailabilityStatus) lastIncomingAudioMessageMedia = attachment; break; } } } } } } if (lastIncomingAudioMessageId != 0) { _currentStreamingAudioMessageId = lastIncomingAudioMessageId; if (lastIncomingAudioMessageMedia != nil) [self playAudioFromMessage:lastIncomingAudioMessageId media:lastIncomingAudioMessageMedia]; } else _currentStreamingAudioMessageId = 0; } if (_currentStreamingAudioMessageId == 0) { [_raiseToListenRecordAfterPlaybackTimer invalidate]; __weak TGModernConversationController *weakSelf = self; _raiseToListenRecordAfterPlaybackTimer = [[TGTimer alloc] initWithTimeout:1.0 repeat:false completion:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_raiseToListenActivator.activated) { [strongSelf startAudioRecording:true completion:^{}]; } } } queue:dispatch_get_main_queue()]; [_raiseToListenRecordAfterPlaybackTimer start]; } }*/ - (void)closeExistingMediaGallery { for (UIWindow *window in [self.associatedWindowStack copy]) { if ([window isKindOfClass:[TGOverlayControllerWindow class]]) { if ([window.rootViewController isKindOfClass:[TGModernGalleryController class]]) { [((TGModernGalleryController *)window.rootViewController) dismiss]; } } } } - (void)closeMediaFromMessage:(int32_t)__unused messageId instant:(bool)__unused instant { [self closeExistingMediaGallery]; self.associatedWindowStack = nil; _companion.mediaHiddenMessageId = 0; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { [(TGMessageModernConversationItem *)[cell boundItem] updateMediaVisibility]; } } - (void)stopInlineMedia { for (TGMessageModernConversationItem *item in _items) { [item stopInlineMedia]; } } - (void)resumeInlineMedia { for (TGMessageModernConversationItem *item in _items) { [item resumeInlineMedia]; } } - (void)updateInlineMediaContexts { for (TGMessageModernConversationItem *item in _items) { [item updateInlineMediaContext]; } } - (void)openBrowserFromMessage:(int32_t)__unused messageId url:(NSString *)url { [(TGApplication *)[TGApplication sharedApplication] openURL:[NSURL URLWithString:url] forceNative:true]; } - (void)showActionsMenuForUnsentMessage:(int32_t)messageId { TGMessageModernConversationItem *unsentMessageItem = nil; int unsentMessageCount = 0; for (TGMessageModernConversationItem *messageItem in _items) { if (messageItem->_message.mid == messageId) unsentMessageItem = messageItem; if (messageItem->_message.deliveryState == TGMessageDeliveryStateFailed) unsentMessageCount++; } if (unsentMessageItem != nil && unsentMessageCount != 0) { NSMutableArray *actions = [[NSMutableArray alloc] init]; if (unsentMessageItem->_message.text.length != 0) [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.MessageDialogEdit") action:@"editMessage"]]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.MessageDialogRetry") action:@"resendMessage"]]; if (unsentMessageCount > 1) [actions addObject:[[TGActionSheetAction alloc] initWithTitle:[[NSString alloc] initWithFormat:TGLocalized(@"Conversation.MessageDialogRetryAll"), unsentMessageCount] action:@"resendAllMessages"]]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.MessageDialogDelete") action:@"deleteMessage"]]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel]]; [[[TGActionSheet alloc] initWithTitle:TGLocalized(@"Conversation.MessageDeliveryFailed") actions:actions actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"editMessage"]) { [controller.companion _deleteMessages:@[@(messageId)] animated:true]; [controller.companion controllerDeletedMessages:@[@(messageId)] completion:nil]; _inputTextPanel.inputField.text = unsentMessageItem->_message.text; } else if ([action isEqualToString:@"resendMessage"]) { [controller.companion controllerWantsToResendMessages:@[@(messageId)]]; } else if ([action isEqualToString:@"resendAllMessages"]) { NSMutableArray *messageIds = [[NSMutableArray alloc] init]; for (TGMessageModernConversationItem *messageItem in controller->_items.reverseObjectEnumerator) { if (messageItem->_message.deliveryState == TGMessageDeliveryStateFailed) { [messageIds addObject:@(messageItem->_message.mid)]; } } [controller.companion controllerWantsToResendMessages:messageIds]; } else if ([action isEqualToString:@"deleteMessage"]) { [controller.companion _deleteMessages:@[@(messageId)] animated:true]; [controller.companion controllerDeletedMessages:@[@(messageId)] completion:nil]; } } target:self] showInView:_view]; } } - (void)highlightAndShowActionsMenuForMessage:(int32_t)messageId { TGMessageModernConversationItem *highlightedItem = nil; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *messageItem = cell.boundItem; if (messageItem != nil && messageItem->_message.mid == messageId) { CGRect contentFrame = [[cell contentViewForBinding] convertRect:[messageItem effectiveContentFrame] toView:_view]; if (CGRectIsNull(contentFrame) || CGRectIsEmpty(contentFrame)) break; contentFrame = CGRectIntersection(contentFrame, CGRectMake(0, 0, _view.frame.size.width, _currentInputPanel == nil ? _view.frame.size.height : _currentInputPanel.frame.origin.y)); if (CGRectIsNull(contentFrame) || CGRectIsEmpty(contentFrame)) break; if (_menuContainerView == nil) _menuContainerView = [[TGMenuContainerView alloc] init]; if (_menuContainerView.superview != _view) [_view addSubview:_menuContainerView]; _menuContainerView.frame = CGRectMake(0, self.controllerInset.top, _view.frame.size.width, _view.frame.size.height - self.controllerInset.top - self.controllerInset.bottom); NSMutableArray *actions = [[NSMutableArray alloc] init]; TGActionMediaAttachment *actionInfo = messageItem->_message.actionInfo; bool canReply = true; if (![_companion allowMessageForwarding]) { canReply = actionInfo == nil; } if (canReply && [_companion allowReplies] && messageItem->_message.cid == [self peerId] && (messageItem->_message.mid < TGMessageLocalMidBaseline || ![_companion allowMessageForwarding])) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuReply"), @"title", @"reply", @"action", nil]]; if ([_companion canDeleteMessage:messageItem->_message] && messageItem->_message.actionInfo != nil) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuDelete"), @"title", @"delete", @"action", nil]]; } if ([_companion canEditMessage:messageItem->_message]) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.Edit"), @"title", @"edit", @"action", nil]]; _currentEditingMessageContext = [[SVariable alloc] init]; [_currentEditingMessageContext set:[_companion editingContextForMessageWithId:messageItem->_message.mid]]; } } else if (messageItem->_message.cid == [self peerId]) { if ([_companion canDeleteMessage:messageItem->_message]) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuDelete"), @"title", @"delete", @"action", nil]]; } if ([_companion canEditMessage:messageItem->_message]) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.Edit"), @"title", @"edit", @"action", nil]]; _currentEditingMessageContext = [[SVariable alloc] init]; [_currentEditingMessageContext set:[_companion editingContextForMessageWithId:messageItem->_message.mid]]; } } bool hasCaption = false; for (TGMediaAttachment *attachment in messageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { TGImageMediaAttachment *imageAttachment = (TGImageMediaAttachment *)attachment; hasCaption = (imageAttachment.caption.length > 0); } } bool isDocument = false; bool isAnimation = false; int64_t remoteDocumentId = 0; id stickerPackReference = nil; for (id attachment in messageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *document = attachment; remoteDocumentId = document.documentId; isAnimation = [document isAnimated] && ([document.mimeType isEqualToString:@"video/mp4"]); NSString *localFilePath = [[_companion fileUrlForDocumentMedia:attachment] path]; if ([[NSFileManager defaultManager] fileExistsAtPath:localFilePath isDirectory:NULL]) isDocument = true; for (id attribute in ((TGDocumentMediaAttachment *)attachment).attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) { stickerPackReference = ((TGDocumentAttributeSticker *)attribute).packReference; } } break; } } bool addedForward = false; if (TGPeerIdIsChannel(messageItem->_message.fromUid) && [_companion allowReplies]) { addedForward = true; } if (!addedForward && TGPeerIdIsChannel(messageItem->_message.fromUid) && messageItem->_message.actionInfo == nil) { addedForward = true; [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuForward"), @"title", @"forward", @"action", nil]]; } if (isAnimation && remoteDocumentId != 0) { if (!TGIsPad() && iosMajorVersion() >= 8) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.LinkDialogSave"), @"title", @"saveGif", @"action", nil]]; } } if (messageItem->_message.text.length != 0 || hasCaption) [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuCopy"), @"title", @"copy", @"action", nil]]; else if (stickerPackReference != nil) { if ([TGStickersSignals isStickerPackInstalled:stickerPackReference]) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuStickerPackInfo"), @"title", @"stickerPackInfo", @"action", nil]]; } else { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuStickerPackAdd"), @"title", @"stickerPackInfo", @"action", nil]]; } } else if (messageItem->_message.actionInfo == nil && [_companion allowMessageForwarding] && !addedForward) { [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuForward"), @"title", @"forward", @"action", nil]]; } if (messageItem->_message.actionInfo == nil) [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.ContextMenuMore"), @"title", @"select", @"action", nil]]; if (TGIsArabic()) { NSMutableArray *reversedActions = [[NSMutableArray alloc] init]; for (id item in actions.reverseObjectEnumerator) { [reversedActions addObject:item]; } actions = reversedActions; } if (actions.count != 0) { [_menuContainerView.menuView setUserInfo:@{@"mid": @(messageId)}]; [_menuContainerView.menuView setButtonsAndActions:actions watcherHandle:_actionHandle]; [_menuContainerView.menuView sizeToFit]; [_menuContainerView showMenuFromRect:[_menuContainerView convertRect:contentFrame fromView:_view]]; highlightedItem = messageItem; [highlightedItem setTemporaryHighlighted:true viewStorage:_viewStorage]; } break; } } for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *messageItem = cell.boundItem; if (messageItem != highlightedItem) [messageItem setTemporaryHighlighted:false viewStorage:_viewStorage]; } } - (void)temporaryHighlightMessage:(int32_t)messageId automatically:(bool)automatically { for (TGMessageModernConversationItem *item in _items) { if (item->_message.mid == messageId) { _temporaryHighlightMessageIdUponDisplay = 0; [item setTemporaryHighlighted:true viewStorage:_viewStorage]; if (automatically) { TGDispatchAfter(0.7, dispatch_get_main_queue(), ^ { [item setTemporaryHighlighted:false viewStorage:_viewStorage]; }); } } } } - (void)showActionsMenuForLink:(NSString *)url webPage:(TGWebPageMediaAttachment *)webPage { if ([url hasPrefix:@"tel:"]) { TGActionSheet *actionSheet = [[TGActionSheet alloc] initWithTitle:url.length < 70 ? url : [[url substringToIndex:70] stringByAppendingString:@"..."] actions:@[ [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.Call") action:@"call"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.LinkDialogCopy") action:@"copy"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel] ] actionBlock:^(__unused TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"call"]) { [TGAppDelegateInstance performPhoneCall:[NSURL URLWithString:url]]; } else if ([action isEqualToString:@"copy"]) { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; if (pasteboard != nil) { NSString *copyString = url; if ([url hasPrefix:@"mailto:"]) copyString = [url substringFromIndex:7]; else if ([url hasPrefix:@"tel:"]) copyString = [url substringFromIndex:4]; [pasteboard setString:copyString]; } } } target:self]; [actionSheet showInView:self.view]; } else { NSString *displayString = url; if ([url hasPrefix:@"hashtag://"]) displayString = [@"#" stringByAppendingString:[url substringFromIndex:@"hashtag://".length]]; else if ([url hasPrefix:@"mention://"]) displayString = [@"@" stringByAppendingString:[url substringFromIndex:@"mention://".length]]; NSMutableArray *actions = [[NSMutableArray alloc] init]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.LinkDialogOpen") action:@"open"]]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.LinkDialogCopy") action:@"copy"]]; if (webPage != nil && webPage.document != nil && ([webPage.document.mimeType isEqualToString:@"video/mp4"]) && [webPage.document isAnimated]) { if (!TGIsPad() && iosMajorVersion() >= 8) { [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.LinkDialogSave") action:@"saveGif"]]; } } [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel]]; TGActionSheet *actionSheet = [[TGActionSheet alloc] initWithTitle:displayString.length < 70 ? displayString : [[displayString substringToIndex:70] stringByAppendingString:@"..."] actions:actions actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"open"]) { [controller openBrowserFromMessage:0 url:url]; } else if ([action isEqualToString:@"copy"]) { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; if (pasteboard != nil) { NSString *copyString = url; if ([url hasPrefix:@"mailto:"]) copyString = [url substringFromIndex:7]; else if ([url hasPrefix:@"tel:"]) copyString = [url substringFromIndex:4]; else if ([url hasPrefix:@"hashtag://"]) copyString = [@"#" stringByAppendingString:[url substringFromIndex:@"hashtag://".length]]; else if ([url hasPrefix:@"mention://"]) copyString = [@"@" stringByAppendingString:[url substringFromIndex:@"mention://".length]]; [pasteboard setString:copyString]; } } else if ([action isEqualToString:@"saveGif"]) { [TGRecentGifsSignal addRecentGifFromDocument:webPage.document]; } } target:self]; [actionSheet showInView:self.view]; } } - (void)showActionsMenuForContact:(TGUser *)contact isContact:(bool)isContact { NSMutableArray *actions = [[NSMutableArray alloc] init]; if (!isContact) [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.AddContact") action:@"addContact"]]; if (contact.uid > 0) [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.SendMessage") action:@"sendMessage"]]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.Call") action:@"call"]]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel]]; TGActionSheet *actionSheet = [[TGActionSheet alloc] initWithTitle:nil actions:actions actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"addContact"]) [controller showAddContactMenu:contact]; else if ([action isEqualToString:@"sendMessage"]) [controller.companion controllerRequestedNavigationToConversationWithUser:contact.uid]; else if ([action isEqualToString:@"call"]) { NSString *url = [[NSString alloc] initWithFormat:@"tel:%@", [TGPhoneUtils formatPhoneUrl:contact.phoneNumber]]; [TGAppDelegateInstance performPhoneCall:[NSURL URLWithString:url]]; } } target:self]; [actionSheet showInView:self.view]; } - (void)showAddContactMenu:(TGUser *)contact { [[[TGActionSheet alloc] initWithTitle:nil actions:@[ [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Profile.CreateNewContact") action:@"createNewContact"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Profile.AddToExisting") action:@"addToExisting"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel] ] actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"createNewContact"]) [controller.companion controllerWantsToCreateContact:contact.uid firstName:contact.firstName lastName:contact.lastName phoneNumber:contact.phoneNumber]; else if ([action isEqualToString:@"addToExisting"]) [controller.companion controllerWantsToAddContactToExisting:contact.uid phoneNumber:contact.phoneNumber]; } target:self] showInView:self.view]; } - (void)showCallNumberMenu:(NSArray *)phoneNumbers { NSMutableArray *actions = [[NSMutableArray alloc] init]; for (NSArray *desc in phoneNumbers) { [actions addObject:[[TGActionSheetAction alloc] initWithTitle:((NSString *)desc[0]).length == 0 ? desc[2] : [[NSString alloc] initWithFormat:@"%@: %@", desc[0], desc[2]] action:desc[1]]]; } [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel]]; [[[TGActionSheet alloc] initWithTitle:nil actions:actions actionBlock:^(__unused id target, NSString *action) { if (![action isEqualToString:@"cancel"]) { NSString *url = [[NSString alloc] initWithFormat:@"tel:%@", [TGPhoneUtils formatPhoneUrl:action]]; [TGAppDelegateInstance performPhoneCall:[NSURL URLWithString:url]]; } } target:self] showInView:self.view]; } - (void)enterEditingMode { [self _enterEditingMode:0]; } - (void)leaveEditingMode { [self _leaveEditingModeAnimated:false]; } - (void)openKeyboard { if (!_editingMode) { _inputTextPanel.inputField.internalTextView.enableFirstResponder = true; [_inputTextPanel.inputField becomeFirstResponder]; } } - (void)hideTitlePanel { [self setCurrentTitlePanel:nil animation:TGModernConversationPanelAnimationSlide]; } - (void)reloadBackground { UIImageView *tempImageView = [[UIImageView alloc] initWithImage:_backgroundView.image]; tempImageView.contentMode = _backgroundView.contentMode; tempImageView.frame = _backgroundView.frame; [_backgroundView.superview insertSubview:tempImageView aboveSubview:_backgroundView]; _backgroundView.image = [[TGWallpaperManager instance] currentWallpaperImage]; [UIView animateWithDuration:0.3 animations:^ { tempImageView.alpha = 0.0f; } completion:^(__unused BOOL finished) { [tempImageView removeFromSuperview]; }]; for (TGMessageModernConversationItem *item in _items) { [item updateAssets]; } [_collectionView updateDecorationAssets]; } - (void)refreshMetrics { for (TGMessageModernConversationItem *item in _items) { [item refreshMetrics]; } [_collectionLayout invalidateLayout]; [_collectionView layoutSubviews]; } - (void)setMessageEditingContext:(TGMessageEditingContext *)messageEditingContext { if (_inputTextPanel == nil) { _initialMessageEdigingContext = messageEditingContext; } else { [self setEditMessageWithText:messageEditingContext.text isCaption:messageEditingContext.isCaption messageId:messageEditingContext.messageId animated:false]; } } - (void)setInputText:(NSString *)inputText replace:(bool)replace selectRange:(NSRange)selectRange { if (_inputTextPanel == nil) { _initialInputText = inputText; _initialSelectRange = selectRange; } else if (TGStringCompare(_inputTextPanel.maybeInputField.text, @"") || replace) { [[_inputTextPanel inputField] setText:inputText animated:false]; [[_inputTextPanel inputField] selectRange:selectRange]; if (_collectionView != nil) { [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset], 0.0f, _collectionView.contentInset.top, 0.0f) duration:0.0f curve:0]; } else { [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(self.controllerInset.top, 0.0f, _currentInputPanel.frame.size.height, 0.0f) duration:0.0f curve:0]; } } } - (NSString *)inputText { return _inputTextPanel == nil ? _initialInputText : _inputTextPanel.maybeInputField.text; } - (void)updateWebpageLinks { [_inputTextPanel.inputField setText:[self inputText] animated:false]; } - (void)setReplyMessage:(TGMessage *)replyMessage animated:(bool)animated { TGModenConcersationReplyAssociatedPanel *panel = [[TGModenConcersationReplyAssociatedPanel alloc] initWithMessage:replyMessage]; __weak TGModernConversationController *weakSelf = self; panel.dismiss = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf setPrimaryExtendedPanel:nil animated:true]; [strongSelf setSecondaryExtendedPanel:nil animated:true]; } }; [self setPrimaryExtendedPanel:panel animated:animated]; } - (void)setEditMessageWithText:(NSString *)text isCaption:(bool)isCaption messageId:(int32_t)messageId animated:(bool)animated { [self setPrimaryExtendedPanel:nil animated:true]; [self setSecondaryExtendedPanel:nil animated:true]; [_inputTextPanel setMessageEditingContext:[[TGMessageEditingContext alloc] initWithText:text isCaption:isCaption messageId:messageId] animated:animated]; [self setLeftBarButtonItem:[self defaultLeftBarButtonItem] animated:animated]; [_titleView setShowUnreadCount:false]; } - (void)endMessageEditing:(bool)animated { [_inputTextPanel setMessageEditingContext:nil animated:animated]; [self setLeftBarButtonItem:[self defaultLeftBarButtonItem] animated:animated]; [_titleView setShowUnreadCount:true]; } - (void)setForwardMessages:(NSArray *)forwardMessages animated:(bool)animated { if (_inputTextPanel == nil) _initialForwardMessages = forwardMessages; else { TGModernConversationForwardInputPanel *panel = [[TGModernConversationForwardInputPanel alloc] initWithMessages:forwardMessages]; __weak TGModernConversationController *weakSelf = self; panel.dismiss = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf setPrimaryExtendedPanel:nil animated:true]; [strongSelf setSecondaryExtendedPanel:nil animated:true]; } }; [self setPrimaryExtendedPanel:panel animated:animated]; } } - (void)setInlineStickerList:(NSArray *)inlineStickerList { if (inlineStickerList.count == 0 || _inputTextPanel.messageEditingContext != nil) { if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGStickerAssociatedInputPanel class]]) [_inputTextPanel setAssociatedPanel:nil animated:true]; } else { __weak TGModernConversationController *weakSelf = self; [_inputTextPanel setAssociatedStickerList:inlineStickerList stickerSelected:^(TGDocumentMediaAttachment *document) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [[SQueue concurrentDefaultQueue] dispatch:^{ [TGStickersSignals addUseCountForDocumentId:document.documentId]; }]; [strongSelf->_inputTextPanel.maybeInputField setText:@"" animated:true]; [strongSelf->_companion controllerWantsToSendRemoteDocument:document asReplyToMessageId:[strongSelf currentReplyMessageId] text:nil botContextResult:nil]; } }]; } } - (void)setTitle:(NSString *)title { [_titleView setTitle:title]; } - (void)setTitleIcons:(NSArray *)titleIcons { [_titleView setIcons:titleIcons]; } - (void)setTitleModalProgressStatus:(NSString *)titleModalProgressStatus { [_titleView setModalProgressStatus:titleModalProgressStatus]; } - (void)setAvatarConversationId:(int64_t)conversationId title:(NSString *)title icon:(UIImage *)icon { [_avatarButton setAvatarConversationId:conversationId]; [_avatarButton setAvatarTitle:title]; [_avatarButton setAvatarIcon:icon]; } - (void)setAvatarConversationId:(int64_t)conversationId firstName:(NSString *)firstName lastName:(NSString *)lastName { [_avatarButton setAvatarConversationId:conversationId]; [_avatarButton setAvatarFirstName:firstName lastName:lastName]; } - (void)setAvatarUrl:(NSString *)avatarUrl { [_avatarButton setAvatarUrl:avatarUrl]; } - (void)setStatus:(NSString *)status accentColored:(bool)accentColored allowAnimation:(bool)allowAnimation toggleMode:(TGModernConversationControllerTitleToggle)toggleMode { [_titleView setStatus:status animated:self.isViewLoaded && allowAnimation]; [_titleView setStatusHasAccentColor:accentColored]; [_titleView setToggleMode:toggleMode]; } - (void)setAttributedStatus:(NSAttributedString *)status allowAnimation:(bool)allowAnimation { [_titleView setAttributedStatus:status animated:self.isViewLoaded && allowAnimation]; [_titleView setStatusHasAccentColor:false]; } - (void)setTypingStatus:(NSString *)typingStatus activity:(int)activity { [_titleView setTypingStatus:typingStatus activity:(TGModernConversationTitleViewActivity)activity animated:self.isViewLoaded]; } - (void)setGlobalUnreadCount:(int)unreadCount { [_titleView setUnreadCount:unreadCount]; } - (TGModernConversationInputPanel *)defaultInputPanel { if (_companion.previewMode || [_companion _controllerShouldHideInputTextByDefault]) { return nil; } else { return _inputTextPanel; } } - (void)setCustomInputPanel:(TGModernConversationInputPanel *)customInputPanel { if (_customInputPanel != customInputPanel) { _customInputPanel = customInputPanel; if (!_editingMode) { [self setInputPanel:_customInputPanel != nil ? _customInputPanel : [self defaultInputPanel] animated:ABS(CFAbsoluteTimeGetCurrent() - _willAppearTimestamp) > 0.18]; } } } - (TGModernConversationInputPanel *)customInputPanel { return _customInputPanel; } - (void)setPrimaryTitlePanel:(TGModernConversationTitlePanel *)titlePanel { if (_primaryTitlePanel != titlePanel) { bool applyAsCurrent = _currentTitlePanel != nil && _currentTitlePanel == _primaryTitlePanel; _primaryTitlePanel = titlePanel; if (applyAsCurrent) [self setCurrentTitlePanel:titlePanel animation:ABS(CFAbsoluteTimeGetCurrent() - _willAppearTimestamp) > 0.18 ? TGModernConversationPanelAnimationSlide : TGModernConversationPanelAnimationNone]; } } - (TGModernConversationTitlePanel *)primaryTitlePanel { return _primaryTitlePanel; } - (void)setSecondaryTitlePanel:(TGModernConversationTitlePanel *)secondaryTitlePanel { [self setSecondaryTitlePanel:secondaryTitlePanel animated:true]; } - (void)setSecondaryTitlePanel:(TGModernConversationTitlePanel *)secondaryTitlePanel animated:(bool)animated { if (_secondaryTitlePanel != secondaryTitlePanel) { bool applyAsCurrent = _currentTitlePanel == nil || _currentTitlePanel == _secondaryTitlePanel; _secondaryTitlePanel = secondaryTitlePanel; if (applyAsCurrent) { NSTimeInterval appearTime = ABS(CFAbsoluteTimeGetCurrent() - _willAppearTimestamp); [self setCurrentTitlePanel:secondaryTitlePanel animation:(animated && appearTime > 0.1) ? (appearTime > 0.4 ? TGModernConversationPanelAnimationSlide : TGModernConversationPanelAnimationFade) : TGModernConversationPanelAnimationNone]; } } } - (TGModernConversationTitlePanel *)secondaryTitlePanel { return _secondaryTitlePanel; } - (void)setCurrentTitlePanel:(TGModernConversationTitlePanel *)currentTitlePanel animation:(TGModernConversationPanelAnimation)animation { if (_companion.previewMode) { return; } if (_currentTitlePanel != currentTitlePanel) { if (_currentTitlePanel != nil) { if (animation != TGModernConversationPanelAnimationNone) { TGModernConversationTitlePanel *lastPanel = _currentTitlePanel; if (animation == TGModernConversationPanelAnimationSlide) { [UIView animateWithDuration:0.09 delay:0.0 options:iosMajorVersion() < 7 ? 0 : (7 << 16) animations:^ { lastPanel.frame = CGRectOffset(lastPanel.frame, 0.0f, -lastPanel.frame.size.height); } completion:^(__unused BOOL finished) { [lastPanel removeFromSuperview]; }]; } else if (animation == TGModernConversationPanelAnimationSlideFar) { _titlePanelWrappingView.clipsToBounds = false; [UIView animateWithDuration:0.3 delay:0.0 options:0 animations:^ { lastPanel.frame = CGRectOffset(lastPanel.frame, 0.0f, -lastPanel.frame.size.height - lastPanel.superview.frame.origin.y); } completion:^(__unused BOOL finished) { [lastPanel removeFromSuperview]; _titlePanelWrappingView.clipsToBounds = true; }]; } else { [UIView animateWithDuration:0.09 delay:0.0 options:iosMajorVersion() < 7 ? 0 : (7 << 16) animations:^ { lastPanel.alpha = 0.0f; } completion:^(__unused BOOL finished) { [lastPanel removeFromSuperview]; lastPanel.alpha = 1.0f; }]; } } else [_currentTitlePanel removeFromSuperview]; } _currentTitlePanel = currentTitlePanel; if (_currentTitlePanel != nil && [self isViewLoaded]) { if (_titlePanelWrappingView == nil) { _titlePanelWrappingView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, self.controllerInset.top, _view.frame.size.width, 44.0f)]; _titlePanelWrappingView.clipsToBounds = true; [_view addSubview:_titlePanelWrappingView]; } _titlePanelWrappingView.userInteractionEnabled = true; [_titlePanelWrappingView addSubview:_currentTitlePanel]; CGRect titlePanelFrame = CGRectMake(0.0f, 0.0f, _titlePanelWrappingView.frame.size.width, _currentTitlePanel.frame.size.height); if (animation != TGModernConversationPanelAnimationNone) { if (animation == TGModernConversationPanelAnimationSlide) { _currentTitlePanel.frame = CGRectOffset(titlePanelFrame, 0.0f, -titlePanelFrame.size.height); [UIView animateWithDuration:0.09 delay:0.0 options:iosMajorVersion() < 7 ? 0 : (7 << 16) animations:^ { _currentTitlePanel.frame = titlePanelFrame; } completion:nil]; } else { _currentTitlePanel.frame = titlePanelFrame; _currentTitlePanel.alpha = 0.0f; [UIView animateWithDuration:0.09 delay:0.0 options:iosMajorVersion() < 7 ? 0 : (7 << 16) animations:^ { _currentTitlePanel.alpha = 1.0f; } completion:nil]; } } else { _currentTitlePanel.alpha = 1.0f; _currentTitlePanel.frame = titlePanelFrame; } } else _titlePanelWrappingView.userInteractionEnabled = false; } } - (void)setEmptyListPlaceholder:(TGModernConversationEmptyListPlaceholderView *)emptyListPlaceholder { [self setEmptyListPlaceholder:emptyListPlaceholder animated:ABS(CFAbsoluteTimeGetCurrent() - _willAppearTimestamp) > 0.18]; } - (void)setEmptyListPlaceholder:(TGModernConversationEmptyListPlaceholderView *)emptyListPlaceholder animated:(bool)animated { if (_emptyListPlaceholder != emptyListPlaceholder) { if (_emptyListPlaceholder != nil) { if (animated) { UIView *currentView = _emptyListPlaceholder; _emptyListPlaceholder = nil; [UIView animateWithDuration:0.3 * 0.7 animations:^ { currentView.alpha = 0.0f; } completion:^(__unused BOOL finished) { [currentView removeFromSuperview]; }]; } else { [_emptyListPlaceholder removeFromSuperview]; _emptyListPlaceholder = nil; } } _emptyListPlaceholder = emptyListPlaceholder; if (self.isViewLoaded) { [_view insertSubview:_emptyListPlaceholder belowSubview:_currentInputPanel]; if (animated) { _emptyListPlaceholder.alpha = 0.0f; [UIView animateWithDuration:0.3 * 0.7 animations:^ { _emptyListPlaceholder.alpha = 1.0f; }]; } else _emptyListPlaceholder.alpha = 1.0f; if (_collectionView != nil) { [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset], 0.0f, _collectionView.contentInset.top, 0.0f) duration:0.0f curve:0]; } else { [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(self.controllerInset.top, 0.0f, 45.0f, 0.0f) duration:0.0f curve:0]; } } } } - (void)setConversationHeader:(UIView *)conversationHeader { _conversationHeader = conversationHeader; if (_collectionView != nil) { _collectionView.headerView = conversationHeader; UIEdgeInsets inset = _collectionView.contentInset; inset.bottom = self.controllerInset.top + 210.0f + [_collectionView implicitTopInset]; _collectionView.contentInset = inset; } } #pragma mark - - (void)setEnableAboveHistoryRequests:(bool)enableAboveHistoryRequests { _enableAboveHistoryRequests = enableAboveHistoryRequests; if (_collectionView != nil) { [self _updateVisibleItemIndices:nil]; } } - (void)setEnableBelowHistoryRequests:(bool)enableBelowHistoryRequests { _enableBelowHistoryRequests = enableBelowHistoryRequests; } - (void)setEnableSendButton:(bool)enableSendButton { _inputTextPanel.sendButton.userInteractionEnabled = enableSendButton; } - (void)_updateCanReadHistory:(TGModernConversationActivityChange)change { bool canReadHistory = true; if (change == TGModernConversationActivityChangeActive) canReadHistory = true; else if (change == TGModernConversationActivityChangeInactive) canReadHistory = false; else { if (canReadHistory && self.navigationController.topViewController != self) canReadHistory = false; if (canReadHistory && ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && [UIApplication sharedApplication].applicationState != UIApplicationStateInactive)) canReadHistory = false; if ([UIApplication sharedApplication] == nil) canReadHistory = false; } if (_canReadHistory != canReadHistory) { if (canReadHistory) { [self resumeInlineMedia]; } else { [self stopInlineMedia]; } _canReadHistory = canReadHistory; [_companion controllerCanReadHistoryUpdated]; } [self updateRaiseToListen]; } - (bool)raiseToListenEnabled { return _canReadHistory && _currentInputPanel == _inputTextPanel && (_currentAudioRecorder == nil || !_currentAudioRecorderIsTouchInitiated) && _attachmentSheetWindow == nil; } - (void)updateRaiseToListen { _raiseToListenActivator.enabled = [self raiseToListenEnabled]; } - (void)_updateCanRegroupIncomingUnreadMessages { [_companion controllerCanRegroupUnreadIncomingMessages]; } - (bool)canReadHistory { return _canReadHistory || ([UIApplication sharedApplication] != nil && [UIApplication sharedApplication].applicationState == UIApplicationStateActive && self.navigationController.topViewController == self); } - (NSArray *)_items { return _items; } - (int32_t)_currentReplyMessageId { return [self currentReplyMessageId]; } - (NSArray *)_currentForwardMessageDescs { NSMutableArray *messageDescs = [[NSMutableArray alloc] init]; for (TGMessage *message in [self currentForwardMessages]) { [messageDescs addObject:@{@"peerId": @(message.toUid), @"messageId": @(message.mid)}]; } return messageDescs; } - (void)setReplyMarkup:(TGBotReplyMarkup *)replyMarkup { _replyMarkup = replyMarkup; [_inputTextPanel setReplyMarkup:_replyMarkup]; } - (void)setHasBots:(bool)hasBots { _hasBots = hasBots; [_inputTextPanel setHasBots:_hasBots]; } - (void)setCanBroadcast:(bool)canBroadcast { _canBroadcast = canBroadcast; [_inputTextPanel setCanBroadcast:canBroadcast]; } - (void)setIsBroadcasting:(bool)isBroadcasting { _isBroadcasting = isBroadcasting; [_inputTextPanel setIsBroadcasting:isBroadcasting]; } - (void)setIsAlwaysBroadcasting:(bool)isAlwaysBroadcasting { _isAlwaysBroadcasting = isAlwaysBroadcasting; [_inputTextPanel setIsAlwaysBroadcasting:isAlwaysBroadcasting]; } - (void)appendCommand:(NSString *)command { NSString *currentText = self.inputText; NSString *currentNormalizedText = [currentText lowercaseString]; if (currentText.length == 0) currentText = command; else { bool foundSuffix = false; for (NSInteger i = (NSInteger)command.length; i > 0; i--) { if ([currentNormalizedText hasSuffix:[[command lowercaseString] substringToIndex:i]]) { currentText = [currentText stringByReplacingCharactersInRange:NSMakeRange(currentText.length - i, i) withString:command]; //currentText = [currentText stringByAppendingString:[command substringFromIndex:i]]; foundSuffix = true; break; } } if (!foundSuffix) { if ([currentText hasSuffix:@" "]) currentText = [currentText stringByAppendingFormat:@" %@ ", command]; else currentText = [currentText stringByAppendingFormat:@"%@ ", command]; } } [self setInputText:currentText replace:true selectRange:NSMakeRange(0, 0)]; [_inputTextPanel inputField].internalTextView.enableFirstResponder = true; } - (void)setEnableUnloadHistoryRequests:(bool)enableUnloadHistoryRequests { _enableUnloadHistoryRequests = enableUnloadHistoryRequests; } - (void)_updateItemsAnimationsEnabled { for (TGMessageModernConversationItem *item in _items) { [item updateAnimationsEnabled]; } } - (void)_maybeUnloadHistory { if (_enableUnloadHistoryRequests && (NSInteger)_items.count >= TGModernConversationControllerUnloadHistoryLimit + TGModernConversationControllerUnloadHistoryThreshold) { NSIndexPath *indexPath = [_collectionView indexPathsForVisibleItems].firstObject; if (indexPath != nil) { if (indexPath.row < (int)(_items.count / 2)) [_companion unloadMessagesAbove]; else [_companion unloadMessagesBelow]; } } } #pragma mark - - (void)titleViewTapped:(TGModernConversationTitleView *)__unused titleView { if (_editingMode) return; if (_titleView.toggleMode != TGModernConversationControllerTitleToggleNone) { [_companion _toggleTitleMode]; } else { if (!_shouldHaveTitlePanelLoaded) { _shouldHaveTitlePanelLoaded = true; [_companion _loadControllerPrimaryTitlePanel]; } if (_primaryTitlePanel != nil) { if (_currentTitlePanel != _primaryTitlePanel) [self setCurrentTitlePanel:_primaryTitlePanel animation:TGModernConversationPanelAnimationSlide]; else [self setCurrentTitlePanel:_secondaryTitlePanel animation:TGModernConversationPanelAnimationSlide]; } } } - (void)editingPanelRequestedDeleteMessages:(TGModernConversationEditingPanel *)__unused editingPanel { [[[TGActionSheet alloc] initWithTitle:nil actions:@[ [[TGActionSheetAction alloc] initWithTitle:[_companion checkedMessageCount] == 1 ? TGLocalized(@"Conversation.DeleteOneMessage") : TGLocalized(@"Conversation.DeleteManyMessages") action:@"delete" type:TGActionSheetActionTypeDestructive], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel] ] actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"delete"]) [controller _commitDeleteCheckedMessages]; } target:self] showInView:self.view]; } - (void)editingPanelRequestedForwardMessages:(TGModernConversationEditingPanel *)__unused editingPanel { [self forwardMessages:[_companion checkedMessageIds] fastForward:false]; } - (void)inputTextPanelHasIndicatedTypingActivity:(TGModernConversationInputTextPanel *)__unused inputTextPanel { [_companion controllerDidUpdateTypingActivity]; } - (void)inputPanelTextChanged:(TGModernConversationInputTextPanel *)__unused inputTextPanel text:(NSString *)text { if (iosMajorVersion() >= 8 && _currentActivity != nil) { [_currentActivity addUserInfoEntriesFromDictionary:@{@"text": text == nil ? @"" : text}]; _currentActivity.needsSave = true; } [_companion controllerDidChangeInputText:text]; if (_inputPlaceholderForTextDisposable == nil) { _inputPlaceholderForTextDisposable = [[SMetaDisposable alloc] init]; } [_inputTextPanel setContextPlaceholder:nil]; if (_inputTextPanel.messageEditingContext != nil) { [_inputPlaceholderForTextDisposable setDisposable:nil]; } else { __weak TGModernConversationController *weakSelf = self; [_inputPlaceholderForTextDisposable setDisposable:[[[_companion inputPlaceholderForText:text] deliverOn:[SQueue mainQueue]] startWithNext:^(NSString *placeholder) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_inputTextPanel setContextPlaceholder:placeholder]; } }]]; } } - (void)inputPanelMentionEntered:(TGModernConversationInputTextPanel *)__unused inputTextPanel mention:(NSString *)mention startOfLine:(bool)startOfLine { if (mention == nil) { if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMentionsAssociatedPanel class]]) [_inputTextPanel setAssociatedPanel:nil animated:true]; } else { TGModernConversationMentionsAssociatedPanel *panel = nil; if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMentionsAssociatedPanel class]]) panel = (TGModernConversationMentionsAssociatedPanel *)[_inputTextPanel associatedPanel]; else { panel = [[TGModernConversationMentionsAssociatedPanel alloc] init]; __weak TGModernConversationController *weakSelf = self; panel.userSelected = ^(TGUser *user) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMentionsAssociatedPanel class]]) { [strongSelf->_inputTextPanel setAssociatedPanel:nil animated:false]; } [strongSelf->_inputTextPanel replaceMention:user.userName]; } }; [_inputTextPanel setAssociatedPanel:panel animated:true]; } [panel setUserListSignal:[_companion userListForMention:mention canBeContextBot:startOfLine]]; } } - (void)inputPanelMentionTextEntered:(TGModernConversationInputTextPanel *)__unused inputTextPanel mention:(NSString *)mention text:(NSString *)text { [_mentionTextResultsDisposable setDisposable:nil]; if (_inputTextPanel.messageEditingContext != nil) { return; } if (mention.length != 0 && text != nil) { CFAbsoluteTime delay = 0.4; NSTimeInterval currentTime = CFAbsoluteTimeGetCurrent(); if (currentTime > _mentionTextResultsRequestTimestamp + 5.0) { delay = 0.0; } _mentionTextResultsRequestTimestamp = currentTime; if (_mentionTextResultsDisposable == nil) { _mentionTextResultsDisposable = [[SMetaDisposable alloc] init]; } SSignal *signal = [[_companion inlineResultForMentionText:mention text:text] deliverOn:[SQueue mainQueue]]; if (signal != nil) { if (delay > DBL_EPSILON) { signal = [[[SSignal complete] delay:delay onQueue:[SQueue mainQueue]] then:signal]; } } __weak TGModernConversationController *weakSelf = self; [_mentionTextResultsDisposable setDisposable:[[signal onDispose:^{ TGDispatchOnMainThread(^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_inputTextPanel setDisplayProgress:false]; } }); }] startWithNext:^(id next) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([next respondsToSelector:@selector(boolValue)]) { [strongSelf->_inputTextPanel setDisplayProgress:[next boolValue]]; } else { void (^resultSelected)(TGBotContextResults *, TGBotContextResult *) = ^(TGBotContextResults *results, TGBotContextResult *result) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { TGUser *user = [TGDatabaseInstance() loadUser:results.userId]; if (user != nil) { [TGRecentContextBotsSignal addRecentBot:results.userId]; } TGBotContextResultAttachment *botContextResult = [[TGBotContextResultAttachment alloc] initWithUserId:results.userId resultId:result.resultId queryId:result.queryId]; if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageAuto class]]) { TGBotContextResultSendMessageAuto *concreteMessage = (TGBotContextResultSendMessageAuto *)result.sendMessage; if ([result isKindOfClass:[TGBotContextDocumentResult class]]) { TGBotContextDocumentResult *concreteResult = (TGBotContextDocumentResult *)result; [strongSelf->_companion controllerWantsToSendRemoteDocument:concreteResult.document asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult]; [strongSelf setInputText:@"" replace:true selectRange:NSMakeRange(0, 0)]; } else if ([result isKindOfClass:[TGBotContextImageResult class]]) { TGBotContextImageResult *concreteResult = (TGBotContextImageResult *)result; [strongSelf->_companion controllerWantsToSendRemoteImage:concreteResult.image text:concreteMessage.caption asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult]; [strongSelf setInputText:@"" replace:true selectRange:NSMakeRange(0, 0)]; } else if ([result isKindOfClass:[TGBotContextExternalResult class]]) { TGBotContextExternalResult *concreteResult = (TGBotContextExternalResult *)result; if ([concreteResult.type isEqualToString:@"gif"]) { TGExternalGifSearchResult *externalGifSearchResult = [[TGExternalGifSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl size:concreteResult.size]; id description = [strongSelf->_companion documentDescriptionFromExternalGifSearchResult:externalGifSearchResult text:concreteMessage.caption botContextResult:botContextResult]; if (description != nil) { [strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId]]; [strongSelf setInputText:@"" replace:true selectRange:NSMakeRange(0, 0)]; [TGRecentContextBotsSignal addRecentBot:results.userId]; } } else if ([concreteResult.type isEqualToString:@"photo"]) { TGExternalImageSearchResult *externalImageSearchResult = [[TGExternalImageSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl title:concreteResult.title size:concreteResult.size]; id description = [strongSelf->_companion imageDescriptionFromExternalImageSearchResult:externalImageSearchResult text:concreteMessage.caption botContextResult:botContextResult]; if (description != nil) { [strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId]]; [strongSelf setInputText:@"" replace:true selectRange:NSMakeRange(0, 0)]; [TGRecentContextBotsSignal addRecentBot:results.userId]; } } else { [strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.caption entities:@[] asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult]; } } } else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageText class]]) { TGBotContextResultSendMessageText *concreteMessage = (TGBotContextResultSendMessageText *)result.sendMessage; [strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.message entities:concreteMessage.entities asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult]; } } }; TGBotContextResults *results = next; if (results.results.count == 0) { [strongSelf->_inputTextPanel setDisplayProgress:false]; if ([[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationGenericContextResultsAssociatedPanel class]] || [[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMediaContextResultsAssociatedPanel class]]) { [strongSelf->_inputTextPanel setAssociatedPanel:nil animated:true]; } } else if (results.isMedia) { TGModernConversationMediaContextResultsAssociatedPanel *panel = nil; if ([[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMediaContextResultsAssociatedPanel class]]) { panel = (TGModernConversationMediaContextResultsAssociatedPanel *)[strongSelf->_inputTextPanel associatedPanel]; } else { panel = [[TGModernConversationMediaContextResultsAssociatedPanel alloc] initWithStyle:TGModernConversationAssociatedInputPanelDefaultStyle]; panel.resultSelected = resultSelected; [strongSelf->_inputTextPanel setAssociatedPanel:panel animated:true]; } [panel setResults:results reload:true]; } else { TGModernConversationGenericContextResultsAssociatedPanel *panel = nil; if ([[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationGenericContextResultsAssociatedPanel class]]) { panel = (TGModernConversationGenericContextResultsAssociatedPanel *)[strongSelf->_inputTextPanel associatedPanel]; } else { panel = [[TGModernConversationGenericContextResultsAssociatedPanel alloc] initWithStyle:TGModernConversationAssociatedInputPanelDefaultStyle]; panel.resultSelected = resultSelected; panel.previewWebpage = ^(NSString *url, bool isEmbed, CGSize embedSize) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (isEmbed) { TGWebPageMediaAttachment *webPage = [[TGWebPageMediaAttachment alloc] init]; webPage.embedType = @"video"; webPage.embedUrl = url; webPage.embedSize = embedSize; [strongSelf openEmbed:webPage]; } else { if (iosMajorVersion() >= 9) { SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:url] entersReaderIfAvailable:false]; [strongSelf presentViewController:controller animated:true completion:nil]; } else { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; } } } }; [strongSelf->_inputTextPanel setAssociatedPanel:panel animated:true]; } [panel setResults:results]; } } } } error:^(__unused id error) { } completed:nil]]; } else { [_inputTextPanel setDisplayProgress:false]; if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationGenericContextResultsAssociatedPanel class]] || [[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationMediaContextResultsAssociatedPanel class]]) { [_inputTextPanel setAssociatedPanel:nil animated:true]; } } } - (void)inputPanelHashtagEntered:(TGModernConversationInputTextPanel *)__unused inputTextPanel hashtag:(NSString *)hashtag { if (hashtag == nil) { if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationHashtagsAssociatedPanel class]]) [_inputTextPanel setAssociatedPanel:nil animated:true]; } else { TGModernConversationHashtagsAssociatedPanel *panel = nil; if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationHashtagsAssociatedPanel class]]) panel = (TGModernConversationHashtagsAssociatedPanel *)[_inputTextPanel associatedPanel]; else { panel = [[TGModernConversationHashtagsAssociatedPanel alloc] init]; __weak TGModernConversationController *weakSelf = self; panel.hashtagSelected = ^(NSString *hashtag) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationHashtagsAssociatedPanel class]]) { [strongSelf->_inputTextPanel setAssociatedPanel:nil animated:false]; } [strongSelf->_inputTextPanel replaceHashtag:hashtag]; } }; [_inputTextPanel setAssociatedPanel:panel animated:true]; } [panel setHashtagListSignal:[_companion hashtagListForHashtag:hashtag]]; } } - (void)inputPanelCommandEntered:(TGModernConversationInputTextPanel *)__unused inputTextPanel command:(NSString *)command { if (command == nil) { if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationCommandsAssociatedPanel class]]) [_inputTextPanel setAssociatedPanel:nil animated:true]; } else { TGModernConversationCommandsAssociatedPanel *panel = nil; if ([[_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationCommandsAssociatedPanel class]]) panel = ((TGModernConversationCommandsAssociatedPanel *)[_inputTextPanel associatedPanel]); else { panel = [[TGModernConversationCommandsAssociatedPanel alloc] init]; __weak TGModernConversationController *weakSelf = self; panel.commandSelected = ^(TGBotComandInfo *command, TGUser *user) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if ([[strongSelf->_inputTextPanel associatedPanel] isKindOfClass:[TGModernConversationCommandsAssociatedPanel class]]) { [strongSelf->_inputTextPanel setAssociatedPanel:nil animated:false]; } NSString *commandText = command.command; if (user != nil && ![strongSelf->_companion isASingleBotGroup]) commandText = [commandText stringByAppendingFormat:@"@%@", user.userName]; [strongSelf->_companion controllerWantsToSendTextMessage:[@"/" stringByAppendingString:commandText] entities:nil asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:@[] disableLinkPreviews:false botContextResult:nil]; //[strongSelf appendCommand:commandText]; } }; [_inputTextPanel setAssociatedPanel:panel animated:true]; } [panel setCommandListSignal:[_companion commandListForCommand:command]]; } } - (void)inputPanelLinkParsed:(TGModernConversationInputTextPanel *)__unused inputTextPanel link:(NSString *)link probablyComplete:(bool)probablyComplete { if (link.length != 0 && ![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) { [_companion maybeAskForSecretWebpages]; return; } if (![_companion allowExternalContent]) return; if (_inputTextPanel.messageEditingContext != nil && _inputTextPanel.messageEditingContext.isCaption) { return; } if (_currentLinkParseDisposable == nil) _currentLinkParseDisposable = [[SMetaDisposable alloc] init]; if (!TGStringCompare(_currentLinkParseLink, link)) { _disableLinkPreviewsForMessage = false; if (link.length == 0) { _currentLinkParseLink = link; [_currentLinkParseDisposable setDisposable:nil]; if ([[_inputTextPanel secondaryExtendedPanel] isKindOfClass:[TGModernConversationWebPreviewInputPanel class]]) { [_inputTextPanel setSecondaryExtendedPanel:nil animated:true]; } } else { SSignal *parseLinkSignal = [TGUpdateStateRequestBuilder requestWebPageByText:link]; if (!probablyComplete) parseLinkSignal = [parseLinkSignal delay:1.4 onQueue:[SQueue mainQueue]]; __weak TGModernConversationController *weakSelf = self; if ([[_inputTextPanel secondaryExtendedPanel] isKindOfClass:[TGModernConversationWebPreviewInputPanel class]]) { [_inputTextPanel setSecondaryExtendedPanel:nil animated:true]; } _currentLinkParseLink = link; [_currentLinkParseDisposable setDisposable:[[parseLinkSignal deliverOn:[SQueue mainQueue]] startWithNext:^(TGWebPageMediaAttachment *webPage) { //TGLog(@"parsed link %@ to webpage (%@)", link, webPage.url == nil ? @"incomplete" : @"complete"); __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (webPage == nil || (webPage.url == nil && webPage.pendingDate == -1)) { if ([[strongSelf->_inputTextPanel secondaryExtendedPanel] isKindOfClass:[TGModernConversationWebPreviewInputPanel class]]) { [strongSelf->_inputTextPanel setSecondaryExtendedPanel:nil animated:true]; } } else { TGModernConversationWebPreviewInputPanel *panel = nil; if ([[strongSelf->_inputTextPanel secondaryExtendedPanel] isKindOfClass:[TGModernConversationWebPreviewInputPanel class]]) { panel = (TGModernConversationWebPreviewInputPanel *)[strongSelf->_inputTextPanel secondaryExtendedPanel]; } else { panel = [[TGModernConversationWebPreviewInputPanel alloc] init]; panel.dismiss = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_inputTextPanel setSecondaryExtendedPanel:nil animated:true]; strongSelf->_disableLinkPreviewsForMessage = true; } }; [strongSelf->_inputTextPanel setSecondaryExtendedPanel:panel animated:true]; } [panel setLink:link webPage:webPage]; } } }]]; } } } - (bool)isInputPanelTextEnabled:(TGModernConversationInputTextPanel *)__unused inputTextPanel { return [TGApplicationFeatures isTextMessageEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:NULL]; } - (void)inputPanelFocused:(TGModernConversationInputTextPanel *)__unused inputTextPanel { __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isTextMessageEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; } } - (void)inputTextPanelHasCancelledTypingActivity:(TGModernConversationInputTextPanel *)__unused inputTextPanel { [_companion controllerDidCancelTypingActivity]; } - (int32_t)currentReplyMessageId { int32_t replyMessageId = 0; id extendedPanel = [_inputTextPanel primaryExtendedPanel]; if ([extendedPanel isKindOfClass:[TGModenConcersationReplyAssociatedPanel class]]) replyMessageId = ((TGModenConcersationReplyAssociatedPanel *)extendedPanel).message.mid; return replyMessageId; } - (TGMessage *)currentReplyMessage { id extendedPanel = [_inputTextPanel primaryExtendedPanel]; if ([extendedPanel isKindOfClass:[TGModenConcersationReplyAssociatedPanel class]]) return ((TGModenConcersationReplyAssociatedPanel *)extendedPanel).message; return nil; } - (NSArray *)currentForwardMessages { id extendedPanel = [_inputTextPanel primaryExtendedPanel]; if ([extendedPanel isKindOfClass:[TGModernConversationForwardInputPanel class]]) return ((TGModernConversationForwardInputPanel *)extendedPanel).messages; return nil; } - (void)inputPanelRequestedSendMessage:(TGModernConversationInputTextPanel *)__unused inputTextPanel text:(NSString *)text { if (_inputTextPanel.messageEditingContext != nil) { __weak TGModernConversationController *weakSelf = self; if (_saveEditedMessageDisposable == nil) { _saveEditedMessageDisposable = [[SMetaDisposable alloc] init]; } TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init]; [progressWindow showWithDelay:0.2]; [_saveEditedMessageDisposable setDisposable:[[[[[_companion saveEditedMessageWithId:_inputTextPanel.messageEditingContext.messageId text:_inputTextPanel.messageEditingContext.text disableLinkPreviews:_disableLinkPreviewsForMessage] timeout:5.0 onQueue:[SQueue mainQueue] orSignal:[SSignal fail:@"timeout"]] deliverOn:[SQueue mainQueue]] onDispose:^{ [progressWindow dismiss:true]; }] startWithNext:nil error:^(id error) { NSString *errorType = [[TGTelegramNetworking instance] extractNetworkErrorType:error]; if ([errorType isEqual:@"MESSAGE_NOT_MODIFIED"]) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf endMessageEditing:true]; } } else { NSString *errorText = TGLocalized(@"Login.UnknownError"); if (![error isEqual:@"timeout"]) { errorText = TGLocalized(@"Channel.EditMessageErrorGeneric"); } [[[TGAlertView alloc] initWithTitle:nil message:errorText cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; } } completed:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf endMessageEditing:true]; } }]]; _disableLinkPreviewsForMessage = false; } else { [_companion controllerWantsToSendTextMessage:text entities:nil asReplyToMessageId:[self currentReplyMessageId] withAttachedMessages:[self currentForwardMessages] disableLinkPreviews:_disableLinkPreviewsForMessage botContextResult:nil]; _disableLinkPreviewsForMessage = false; } } - (void)_asyncProcessMediaAssetSignals:(NSArray *)signals { SQueue *queue = [[SQueue alloc] init]; SSignal *combinedSignal = nil; for (SSignal *signal in signals) { if (combinedSignal == nil) combinedSignal = [signal startOn:queue]; else combinedSignal = [[combinedSignal then:signal] startOn:queue]; } __weak TGModernConversationController *weakSelf = self; [_processMediaDisposable setDisposable:[[[combinedSignal reduceLeft:[[NSMutableArray alloc] init] with:^NSMutableArray *(NSMutableArray *itemDescriptions, id item) { if ([item isKindOfClass:[NSDictionary class]]) [itemDescriptions addObject:item]; return itemDescriptions; }] deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *itemDescriptions) { __strong TGModernConversationController *strongSelf = weakSelf; NSMutableArray *mediaDescriptions = [[NSMutableArray alloc] init]; NSMutableArray *fileDescriptions = [[NSMutableArray alloc] init]; for (NSDictionary *description in itemDescriptions) { if (description[@"localImage"] || description[@"remoteImage"] || description[@"downloadImage"] || description[@"downloadDocument"] || description[@"downloadExternalGif"] || description[@"downloadExternalImage"] || description[@"remoteDocument"] || description[@"remoteCachedDocument"] || description[@"assetImage"] || description[@"assetVideo"]) { [mediaDescriptions addObject:description]; } else { [fileDescriptions addObject:description]; } } if (mediaDescriptions.count > 0) [strongSelf.companion controllerWantsToSendImagesWithDescriptions:mediaDescriptions asReplyToMessageId:[strongSelf currentReplyMessageId]]; if (fileDescriptions.count > 0) [strongSelf.companion controllerWantsToSendDocumentsWithDescriptions:fileDescriptions asReplyToMessageId:[strongSelf currentReplyMessageId]]; }]]; } - (NSDictionary *)_descriptionForItem:(id)item caption:(NSString *)caption hash:(NSString *)hash { if (item == nil) return nil; if ([item isKindOfClass:[UIImage class]]) { return [self.companion imageDescriptionFromImage:(UIImage *)item caption:caption optionalAssetUrl:hash != nil ? [[NSString alloc] initWithFormat:@"image-%@", hash] : nil]; } else if ([item isKindOfClass:[NSDictionary class]]) { NSDictionary *dict = (NSDictionary *)item; NSString *type = dict[@"type"]; if ([type isEqualToString:@"cloudPhoto"]) { return [self.companion imageDescriptionFromMediaAsset:dict[@"asset"] previewImage:dict[@"previewImage"] document:[dict[@"document"] boolValue] fileName:dict[@"fileName"] caption:caption]; } else if ([type isEqualToString:@"video"]) { return [self.companion videoDescriptionFromMediaAsset:dict[@"asset"] previewImage:dict[@"previewImage"] adjustments:dict[@"adjustments"] document:[dict[@"document"] boolValue] fileName:dict[@"fileName"] caption:caption]; return dict; } else if ([type isEqualToString:@"file"]) { return [self.companion documentDescriptionFromFileAtTempUrl:dict[@"tempFileUrl"] fileName:dict[@"fileName"] mimeType:dict[@"mimeType"] isAnimation:dict[@"isAnimation"]]; } } if ([item isKindOfClass:[TGBingSearchResultItem class]]) { id description = [self.companion imageDescriptionFromBingSearchResult:item caption:caption]; return description; } else if ([item isKindOfClass:[TGGiphySearchResultItem class]]) { id description = [self.companion documentDescriptionFromGiphySearchResult:item]; return description; } else if ([item isKindOfClass:[TGExternalGifSearchResult class]]) { return [self.companion documentDescriptionFromExternalGifSearchResult:item text:nil botContextResult:nil]; } else if ([item isKindOfClass:[TGInternalGifSearchResult class]]) { return [self.companion documentDescriptionFromRemoteDocument:((TGInternalGifSearchResult *)item).document]; } else if ([item isKindOfClass:[TGWebSearchInternalImageResult class]]) { id description = [self.companion imageDescriptionFromInternalSearchImageResult:item caption:caption]; return description; } else if ([item isKindOfClass:[TGWebSearchInternalGifResult class]]) { id description = [self.companion documentDescriptionFromInternalSearchResult:item]; return description; } return nil; } - (void)_dismissBannersForCurrentConversation { [[TGInterfaceManager instance] dismissBannerForConversationId:[self peerId]]; } - (void)inputPanelRequestedAttachmentsMenu:(TGModernConversationInputTextPanel *)__unused inputTextPanel { bool showLegacyMenu = (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad && iosMajorVersion() < 8) || iosMajorVersion() < 7); if (!showLegacyMenu) [self _displayAttachmentsMenu]; else [self _displayLegacyAttachmentsMenu]; } - (void)_displayAttachmentsMenu { __weak TGModernConversationController *weakSelf = self; TGMenuSheetController *controller = [[TGMenuSheetController alloc] init]; controller.dismissesByOutsideTap = true; controller.hasSwipeGesture = true; __weak TGMenuSheetController *weakController = controller; NSMutableArray *itemViews = [[NSMutableArray alloc] init]; bool hasContactItem = [self.companion allowContactSharing]; TGAttachmentCarouselItemView *carouselItem = [[TGAttachmentCarouselItemView alloc] initWithCamera:[PGCamera cameraAvailable] selfPortrait:false forProfilePhoto:false assetType:TGMediaAssetAnyType]; carouselItem.condensed = !hasContactItem; carouselItem.parentController = self; carouselItem.allowCaptions = [_companion allowCaptionedMedia]; __weak TGAttachmentCarouselItemView *weakCarouselItem = carouselItem; carouselItem.suggestionContext = [self _suggestionContext]; carouselItem.cameraPressed = ^(TGAttachmentCameraView *cameraView) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongSelf _displayCameraWithView:cameraView menuController:strongController]; }; carouselItem.sendPressed = ^(TGMediaAsset *currentItem, bool asFiles) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; __strong TGAttachmentCarouselItemView *strongCarouselItem = weakCarouselItem; if (strongController == nil) return; [strongController dismissAnimated:true]; TGMediaAssetsControllerIntent intent = asFiles ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent; [strongSelf _asyncProcessMediaAssetSignals:[TGMediaAssetsController resultSignalsForSelectionContext:strongCarouselItem.selectionContext editingContext:strongCarouselItem.editingContext intent:intent currentItem:currentItem storeAssets:[strongSelf->_companion controllerShouldStoreCapturedAssets] useMediaCache:[strongSelf->_companion controllerShouldCacheServerAssets] descriptionGenerator:^id(id result, NSString *caption, NSString *hash) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return nil; return [strongSelf _descriptionForItem:result caption:caption hash:hash]; }]]; }; carouselItem.editorOpened = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf _updateCanReadHistory:TGModernConversationActivityChangeInactive]; }; carouselItem.editorClosed = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf _updateCanReadHistory:TGModernConversationActivityChangeActive]; }; [itemViews addObject:carouselItem]; TGMenuSheetButtonItemView *galleryItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.PhotoOrVideo") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayMediaPicker:false fromFileMenu:false]; }]; [itemViews addObject:galleryItem]; // TGMenuSheetButtonItemView *searchItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.ImageSearch") type:TGMenuSheetButtonTypeDefault action:^ // { // __strong TGModernConversationController *strongSelf = weakSelf; // if (strongSelf == nil) // return; // // __strong TGMenuSheetController *strongController = weakController; // if (strongController == nil) // return; // // [strongController dismissAnimated:true]; // [strongSelf _displayWebImagePicker]; // }]; // [itemViews addObject:searchItem]; TGMenuSheetButtonItemView *fileItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.File") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongSelf _displayFileMenuWithController:strongController]; }]; [itemViews addObject:fileItem]; carouselItem.underlyingViews = @[ galleryItem, fileItem ]; TGMenuSheetButtonItemView *locationItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.Location") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayLocationPicker]; }]; [itemViews addObject:locationItem]; if (hasContactItem) { TGMenuSheetButtonItemView *contactItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.Contact") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayContactPicker]; }]; [itemViews addObject:contactItem]; } TGMenuSheetButtonItemView *cancelItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; }]; [itemViews addObject:cancelItem]; [controller setItemViews:itemViews]; [self.view endEditing:true]; [controller presentInViewController:self sourceView:_inputTextPanel.attachButton animated:true]; _menuController = controller; } - (void)_displayLegacyAttachmentsMenu { NSMutableArray *actions = [[NSMutableArray alloc] initWithArray:@ [ [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"AttachmentMenu.PhotoOrVideo") action:@"photoOrVideo"], // [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"AttachmentMenu.ImageSearch") action:@"searchWeb"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"AttachmentMenu.File") action:@"document"], [[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.Location") action:@"chooseLocation"] ]]; if ([_companion allowContactSharing]) [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Conversation.Contact") action:@"contact"]]; if ([PGCamera cameraAvailable]) [actions insertObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.TakePhotoOrVideo") action:@"camera"] atIndex:0]; [actions addObject:[[TGActionSheetAction alloc] initWithTitle:TGLocalized(@"Common.Cancel") action:@"cancel" type:TGActionSheetActionTypeCancel]]; TGActionSheet *actionSheet = [[TGActionSheet alloc] initWithTitle:nil actions:actions actionBlock:^(TGModernConversationController *controller, NSString *action) { if ([action isEqualToString:@"cancel"]) return; [controller.view endEditing:true]; if ([action isEqualToString:@"camera"]) [controller _displayCameraWithView:nil menuController:nil]; if ([action isEqualToString:@"photoOrVideo"]) [controller _displayMediaPicker:false fromFileMenu:false]; else if ([action isEqualToString:@"searchWeb"]) [controller _displayWebImagePicker]; else if ([action isEqualToString:@"chooseLocation"]) [controller _displayLocationPicker]; else if ([action isEqualToString:@"document"]) [controller _displayMediaPicker:true fromFileMenu:false]; else if ([action isEqualToString:@"contact"]) [controller _displayContactPicker]; } target:self]; actionSheet.dismissBlock = ^(__unused TGModernConversationController *controller, NSString *action) { return ![action isEqualToString:@"cancel"]; }; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) [actionSheet showInView:self.view]; else [actionSheet showFromRect:CGRectOffset([self.view convertRect:[_inputTextPanel attachmentButtonFrame] fromView:_inputTextPanel], 0.0f, -6.0f) inView:self.view animated:true]; } - (TGSuggestionContext *)_suggestionContext { __weak TGModernConversationController *weakSelf = self; TGSuggestionContext *suggestionContext = [[TGSuggestionContext alloc] init]; suggestionContext.userListSignal = ^SSignal *(NSString *mention) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return nil; return [strongSelf->_companion userListForMention:mention canBeContextBot:false]; }; suggestionContext.hashtagListSignal = ^SSignal *(NSString *hashtag) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return nil; return [strongSelf->_companion hashtagListForHashtag:hashtag]; }; return suggestionContext; } - (void)_displayMediaPicker:(bool)file fromFileMenu:(bool)fromFileMenu { if (![TGAccessChecker checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentRead alertDismissCompletion:nil]) return; __weak TGModernConversationController *weakSelf = self; void (^dismissalBlock)(void) = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf dismissViewControllerAnimated:true completion:nil]; [strongSelf _dismissBannersForCurrentConversation]; }; void (^showMediaPicker)(TGMediaAssetGroup *) = ^(TGMediaAssetGroup *group) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; TGMediaAssetsControllerIntent intent = file ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent; TGMediaAssetsController *assetsController = [TGMediaAssetsController controllerWithAssetGroup:group intent:intent]; assetsController.captionsEnabled = [strongSelf->_companion allowCaptionedMedia]; assetsController.suggestionContext = [strongSelf _suggestionContext]; assetsController.dismissalBlock = dismissalBlock; assetsController.localMediaCacheEnabled = [strongSelf->_companion controllerShouldCacheServerAssets]; assetsController.shouldStoreAssets = [strongSelf->_companion controllerShouldStoreCapturedAssets]; assetsController.shouldShowFileTipIfNeeded = (file && !fromFileMenu); assetsController.descriptionGenerator = ^NSDictionary *(id result, NSString *caption, NSString *hash) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return nil; return [strongSelf _descriptionForItem:result caption:caption hash:hash]; }; assetsController.completionBlock = ^(NSArray *signals) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; dismissalBlock(); [strongSelf _asyncProcessMediaAssetSignals:signals]; }; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { assetsController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; assetsController.modalPresentationStyle = UIModalPresentationFormSheet; } [strongSelf presentViewController:assetsController animated:true completion:nil]; }; if ([TGMediaAssetsLibrary authorizationStatus] == TGMediaLibraryAuthorizationStatusNotDetermined) { [TGMediaAssetsLibrary requestAuthorizationForAssetType:TGMediaAssetAnyType completion:^(__unused TGMediaLibraryAuthorizationStatus status, TGMediaAssetGroup *cameraRollGroup) { if (![TGAccessChecker checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentRead alertDismissCompletion:nil]) return; showMediaPicker(cameraRollGroup); }]; } else { showMediaPicker(nil); } } - (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController { __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isPhotoUploadEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; return; } if (![TGAccessChecker checkCameraAuthorizationStatusWithAlertDismissComlpetion:nil]) return; if (TGAppDelegateInstance.rootController.isSplitView) return; if ([TGCameraController useLegacyCamera]) { [self _displayLegacyCamera]; [menuController dismissAnimated:true]; return; } TGCameraController *controller = nil; CGSize screenSize = TGScreenSize(); if (cameraView.previewView != nil) controller = [[TGCameraController alloc] initWithCamera:cameraView.previewView.camera previewView:cameraView.previewView intent:TGCameraControllerGenericIntent]; else controller = [[TGCameraController alloc] init]; controller.isImportant = true; controller.shouldStoreCapturedAssets = [_companion controllerShouldStoreCapturedAssets]; controller.allowCaptions = [_companion allowCaptionedMedia]; controller.suggestionContext = [self _suggestionContext]; TGCameraControllerWindow *controllerWindow = [[TGCameraControllerWindow alloc] initWithParentController:self contentController:controller]; controllerWindow.hidden = false; controllerWindow.clipsToBounds = true; if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) controllerWindow.frame = CGRectMake(0, 0, screenSize.width, screenSize.height); else controllerWindow.frame = TGAppDelegateInstance.rootController.applicationBounds; bool standalone = true; CGRect startFrame = CGRectMake(0, screenSize.height, screenSize.width, screenSize.height); if (cameraView != nil) { standalone = false; if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) startFrame = CGRectZero; else startFrame = [controller.view convertRect:cameraView.previewView.frame fromView:cameraView]; } [cameraView detachPreviewView]; [controller beginTransitionInFromRect:startFrame]; __weak TGModernConversationController *weakSelf = self; __weak TGCameraController *weakCameraController = controller; __weak TGAttachmentCameraView *weakCameraView = cameraView; controller.beginTransitionOut = ^CGRect { __strong TGCameraController *strongCameraController = weakCameraController; if (strongCameraController == nil) return CGRectZero; __strong TGAttachmentCameraView *strongCameraView = weakCameraView; if (strongCameraView != nil) { [strongCameraView willAttachPreviewView]; if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) return CGRectZero; return [strongCameraController.view convertRect:strongCameraView.frame fromView:strongCameraView.superview]; } return CGRectZero; }; controller.finishedTransitionOut = ^ { __strong TGAttachmentCameraView *strongCameraView = weakCameraView; if (strongCameraView == nil) return; [strongCameraView attachPreviewViewAnimated:true]; }; controller.finishedWithPhoto = ^(UIImage *resultImage, NSString *caption) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isPhotoUploadEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; return; } NSDictionary *imageDescription = [strongSelf->_companion imageDescriptionFromImage:resultImage caption:caption optionalAssetUrl:nil]; NSMutableArray *descriptions = [[NSMutableArray alloc] init]; if (imageDescription != nil) [descriptions addObject:imageDescription]; [strongSelf->_companion controllerWantsToSendImagesWithDescriptions:descriptions asReplyToMessageId:[strongSelf currentReplyMessageId]]; [menuController dismissAnimated:false]; }; controller.finishedWithVideo = ^(NSString *existingAssetId, NSString *tempFilePath, NSUInteger fileSize, UIImage *previewImage, NSTimeInterval duration, CGSize dimensions, NSString *caption) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isFileUploadEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; return; } [strongSelf->_companion controllerWantsToSendLocalVideoWithTempFilePath:tempFilePath fileSize:(int32_t)fileSize previewImage:previewImage duration:duration dimensions:dimensions caption:caption assetUrl:existingAssetId liveUploadData:nil asReplyToMessageId:[strongSelf currentReplyMessageId]]; [menuController dismissAnimated:true]; }; } - (void)_displayLegacyCamera { __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isPhotoUploadEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; return; } TGLegacyCameraController *legacyCameraController = [[TGLegacyCameraController alloc] init]; legacyCameraController.sourceType = UIImagePickerControllerSourceTypeCamera; legacyCameraController.mediaTypes = [[NSArray alloc] initWithObjects:(__bridge NSString *)kUTTypeImage, (__bridge NSString *)kUTTypeMovie, nil]; legacyCameraController.storeCapturedAssets = [_companion controllerShouldStoreCapturedAssets]; legacyCameraController.completionDelegate = self; legacyCameraController.videoMaximumDuration = 100 * 60 * 60; [legacyCameraController setVideoQuality:UIImagePickerControllerQualityTypeMedium]; legacyCameraController.completionDelegate = self; [self presentViewController:legacyCameraController animated:true completion:nil]; } - (void)_displayICloudDrivePicker { NSArray *documentTypes = @ [ @"public.composite-content", @"public.text", @"public.image", @"public.audio", @"public.video", @"public.movie", @"public.font", @"org.telegram.Telegram.webp", @"com.apple.iwork.pages.pages", @"com.apple.iwork.numbers.numbers", @"com.apple.iwork.keynote.key" ]; UIDocumentPickerViewController *controller = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeOpen]; controller.view.backgroundColor = [UIColor whiteColor]; controller.delegate = self; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) controller.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:controller animated:true completion:nil]; } - (void)_displayDropboxPicker { _dropboxProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(dropboxFilesReceived:) name:TGDropboxFilesReceivedNotification]; [TGDropboxHelper openExternalPicker]; } - (void)_displayGoogleDrivePicker { __weak TGModernConversationController *weakSelf = self; TGGoogleDriveController *controller = [[TGGoogleDriveController alloc] init]; controller.dismiss = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf dismissViewControllerAnimated:true completion:nil]; [strongSelf _dismissBannersForCurrentConversation]; }; controller.filePicked = ^(TGGoogleDriveItem *item) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; id description = [strongSelf.companion documentDescriptionFromGoogleDriveItem:item]; [strongSelf.companion controllerWantsToSendCloudDocumentsWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId]]; }; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { controller.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; controller.modalPresentationStyle = UIModalPresentationFormSheet; } [self presentViewController:controller animated:true completion:nil]; } - (void)_displayFileMenuWithController:(TGMenuSheetController *)menuController { if ((iosMajorVersion() >= 8) || (iosMajorVersion() >= 7 && ([TGDropboxHelper isDropboxInstalled] || [TGGoogleDriveController isGoogleDriveInstalled]))) { NSMutableArray *itemViews = [[NSMutableArray alloc] init]; __weak TGModernConversationController *weakSelf = self; __weak TGMenuSheetController *weakController = menuController; bool tipViewClosed = [[[NSUserDefaults standardUserDefaults] objectForKey:@"didShowDocumentPickerTip_v2"] boolValue]; if (!tipViewClosed) { TGAttachmentFileTipView *fileTipItem = [[TGAttachmentFileTipView alloc] init]; fileTipItem.didClose = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [[NSUserDefaults standardUserDefaults] setObject:@true forKey:@"didShowDocumentPickerTip_v2"]; NSMutableArray *itemViews = [strongController.itemViews mutableCopy]; [itemViews removeObjectAtIndex:0]; [strongController setItemViews:itemViews]; }; [itemViews addObject:fileTipItem]; } TGMenuSheetButtonItemView *photoOrVideoItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.PhotoOrVideo") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayMediaPicker:true fromFileMenu:true]; }]; [itemViews addObject:photoOrVideoItem]; if (iosMajorVersion() >= 8) { TGMenuSheetButtonItemView *iCloudItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.FileICloudDrive") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayICloudDrivePicker]; }]; [itemViews addObject:iCloudItem]; } if ([TGDropboxHelper isDropboxInstalled]) { TGMenuSheetButtonItemView *dropboxItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.FileDropbox") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayDropboxPicker]; }]; [itemViews addObject:dropboxItem]; } if ([TGGoogleDriveController isGoogleDriveInstalled]) { TGMenuSheetButtonItemView *googleDriveItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.FileGoogleDrive") type:TGMenuSheetButtonTypeDefault action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; [strongSelf _displayGoogleDrivePicker]; }]; [itemViews addObject:googleDriveItem]; } TGMenuSheetButtonItemView *cancelItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel action:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGMenuSheetController *strongController = weakController; if (strongController == nil) return; [strongController dismissAnimated:true]; }]; [itemViews addObject:cancelItem]; [menuController setItemViews:itemViews animated:true]; } else { [menuController dismissAnimated:true]; [self _displayMediaPicker:true fromFileMenu:false]; } } - (void)dropboxFilesReceived:(NSNotification *)notification { if (notification.object == nil && ![notification.object isKindOfClass:[NSArray class]] && _dropboxProxy != nil) return; NSArray *items = (NSArray *)notification.object; NSMutableArray *descriptions = [[NSMutableArray alloc] init]; for (TGDropboxItem *item in items) { id description = [self.companion documentDescriptionFromDropboxItem:item]; if (description != nil) [descriptions addObject:description]; } [self.companion controllerWantsToSendCloudDocumentsWithDescriptions:descriptions asReplyToMessageId:[self currentReplyMessageId]]; } - (void)documentPicker:(UIDocumentPickerViewController *)__unused controller didPickDocumentAtURL:(NSURL *)url { __weak TGModernConversationController *weakSelf = self; _currentICloudItemRequest = [TGICloudItemRequest requestICloudItemWithUrl:url completion:^(TGICloudItem *item) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; strongSelf->_currentICloudItemRequest = nil; id description = [strongSelf.companion documentDescriptionFromICloudDriveItem:item]; if (description != nil) [strongSelf.companion controllerWantsToSendCloudDocumentsWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId]]; }]; } - (void)imagePickerController:(TGImagePickerController *)__unused imagePicker didFinishPickingWithAssets:(NSArray *)assets { NSMutableArray *imageDescriptions = [[NSMutableArray alloc] init]; for (id abstractAsset in assets) { if ([abstractAsset isKindOfClass:[UIImage class]]) { @autoreleasepool { NSDictionary *imageDescription = [_companion imageDescriptionFromImage:abstractAsset caption:nil optionalAssetUrl:nil]; if (imageDescription != nil) [imageDescriptions addObject:imageDescription]; } } else if ([abstractAsset isKindOfClass:[NSString class]]) { @autoreleasepool { UIImage *image = [[TGRemoteImageView sharedCache] cachedImage:abstractAsset availability:TGCacheDisk]; if (image != nil) { NSDictionary *imageDescription = [_companion imageDescriptionFromImage:image caption:nil optionalAssetUrl:nil]; if (imageDescription != nil) [imageDescriptions addObject:imageDescription]; } } } } if (imageDescriptions.count != 0) [_companion controllerWantsToSendImagesWithDescriptions:imageDescriptions asReplyToMessageId:[self currentReplyMessageId]]; [self dismissViewControllerAnimated:true completion:nil]; } - (void)legacyCameraControllerCompletedWithDocument:(NSURL *)fileUrl fileName:(NSString *)fileName mimeType:(NSString *)mimeType { [self dismissViewControllerAnimated:true completion:nil]; [_companion controllerWantsToSendDocumentWithTempFileUrl:fileUrl fileName:fileName mimeType:mimeType asReplyToMessageId:[self currentReplyMessageId]]; } - (void)_displayWebImagePicker { __weak TGModernConversationController *weakSelf = self; TGWebSearchController *searchController = [[TGWebSearchController alloc] init]; searchController.captionsEnabled = [_companion allowCaptionedMedia]; searchController.suggestionContext = [self _suggestionContext]; searchController.dismiss = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf dismissViewControllerAnimated:true completion:nil]; [strongSelf _dismissBannersForCurrentConversation]; }; searchController.completionBlock = ^(TGWebSearchController *sender) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf _asyncProcessMediaAssetSignals:[sender selectedItemSignals:^id (id item, NSString *caption) { if (item == nil) return nil; return [strongSelf _descriptionForItem:item caption:caption hash:nil]; }]]; }; TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[ searchController ]]; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { navigationController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; } [self presentViewController:navigationController animated:true completion:nil]; } - (void)legacyCameraControllerCapturedVideoWithTempFilePath:(NSString *)tempVideoFilePath fileSize:(int32_t)fileSize previewImage:(UIImage *)previewImage duration:(NSTimeInterval)duration dimensions:(CGSize)dimenstions assetUrl:(NSString *)assetUrl { [self dismissViewControllerAnimated:true completion:nil]; [_companion controllerWantsToSendLocalVideoWithTempFilePath:tempVideoFilePath fileSize:fileSize previewImage:previewImage duration:duration dimensions:dimenstions caption:nil assetUrl:assetUrl liveUploadData:nil asReplyToMessageId:[self currentReplyMessageId]]; } - (void)legacyCameraControllerCompletedWithExistingMedia:(id)media { [self dismissViewControllerAnimated:true completion:nil]; if ([media isKindOfClass:[TGVideoMediaAttachment class]]) [_companion controllerWantsToSendRemoteVideoWithMedia:media asReplyToMessageId:[self currentReplyMessageId]]; } - (void)legacyCameraControllerCompletedWithNoResult { [self dismissViewControllerAnimated:true completion:nil]; } - (void)inputPanelRequestedSendImages:(TGModernConversationInputTextPanel *)__unused inputTextPanel images:(NSArray *)images { __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isPhotoUploadEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; return; } NSMutableArray *imageDescriptions = [[NSMutableArray alloc] init]; for (id abstractAsset in images) { if ([abstractAsset isKindOfClass:[UIImage class]]) { @autoreleasepool { NSDictionary *imageDescription = [_companion imageDescriptionFromImage:abstractAsset caption:nil optionalAssetUrl:nil]; if (imageDescription != nil) [imageDescriptions addObject:imageDescription]; } } } if (imageDescriptions.count != 0) [_companion controllerWantsToSendImagesWithDescriptions:imageDescriptions asReplyToMessageId:[self currentReplyMessageId]]; } - (void)inputPanelRequestedSendData:(TGModernConversationInputTextPanel *)__unused inputTextPanel data:(NSData *)data { if (data != nil) { NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%x%x", (int)arc4random(), (int)arc4random()]]; [data writeToFile:filePath atomically:true]; [_companion controllerWantsToSendDocumentWithTempFileUrl:[NSURL fileURLWithPath:filePath] fileName:@"animation.gif" mimeType:@"image/gif" asReplyToMessageId:[self currentReplyMessageId]]; } } - (void)inputPanelRequestedSendSticker:(TGModernConversationInputTextPanel *)__unused inputTextPanel sticker:(TGDocumentMediaAttachment *)sticker { [self->_companion controllerWantsToSendRemoteDocument:sticker asReplyToMessageId:[self currentReplyMessageId] text:nil botContextResult:nil]; } - (void)inputPanelRequestedSendGif:(TGModernConversationInputTextPanel *)__unused inputTextPanel document:(TGDocumentMediaAttachment *)document { [self->_companion controllerWantsToSendRemoteDocument:document asReplyToMessageId:[self currentReplyMessageId] text:nil botContextResult:nil]; } - (void)inputPanelRequestedActivateCommand:(TGModernConversationInputTextPanel *)__unused inputTextPanel command:(NSString *)command userId:(int32_t)__unused userId messageId:(int32_t)messageId { int32_t replyMessageId = 0; if (((TGGenericModernConversationCompanion *)_companion).conversationId < 0) replyMessageId = messageId; if (_replyMarkup.hideKeyboardOnActivation && !_replyMarkup.alreadyActivated) { [self setReplyMarkup:[_replyMarkup activatedMarkup]]; [TGDatabaseInstance() storeBotReplyMarkupActivated:_replyMarkup forPeerId:((TGGenericModernConversationCompanion *)_companion).conversationId]; } [self->_companion controllerWantsToSendTextMessage:[[NSString alloc] initWithFormat:@"%@%@", @"", command] entities:nil asReplyToMessageId:[self currentReplyMessageId] == 0 ? replyMessageId : [self currentReplyMessageId] withAttachedMessages:@[] disableLinkPreviews:false botContextResult:nil]; } - (void)inputPanelRequestedToggleCommandKeyboard:(TGModernConversationInputTextPanel *)__unused inputTextPanel showCommandKeyboard:(bool)showCommandKeyboard { if (_replyMarkup.manuallyHidden != !showCommandKeyboard) { TGBotReplyMarkup *replyMarkup = !showCommandKeyboard ? [_replyMarkup manuallyHide] : [_replyMarkup manuallyUnhide]; [self setReplyMarkup:replyMarkup]; [TGDatabaseInstance() storeBotReplyMarkupManuallyHidden:_replyMarkup forPeerId:((TGGenericModernConversationCompanion *)_companion).conversationId manuallyHidden:!showCommandKeyboard]; } } - (void)_displayLocationPicker { __weak TGModernConversationController *weakSelf = self; TGLocationPickerControllerIntent intent = [_companion allowVenueSharing] ? TGLocationPickerControllerDefaultIntent : TGLocationPickerControllerCustomLocationIntent; TGLocationPickerController *controller = [[TGLocationPickerController alloc] initWithIntent:intent]; controller.locationPicked = ^(CLLocationCoordinate2D coordinate, TGVenueAttachment *venue) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf->_companion controllerWantsToSendMapWithLatitude:coordinate.latitude longitude:coordinate.longitude venue:venue asReplyToMessageId:[strongSelf currentReplyMessageId]]; [strongSelf dismissViewControllerAnimated:true completion:nil]; }; TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[controller]]; navigationController.restrictLandscape = true; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { navigationController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; } [self presentViewController:navigationController animated:true completion:nil]; } - (void)_displayContactPicker { if (![TGAccessChecker checkAddressBookAuthorizationStatusWithAlertDismissComlpetion:nil]) return; TGForwardContactPickerController *contactPickerController = [[TGForwardContactPickerController alloc] init]; contactPickerController.delegate = self; TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[contactPickerController]]; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { navigationController.presentationStyle = TGNavigationControllerPresentationStyleInFormSheet; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; } [self presentViewController:navigationController animated:true completion:nil]; } - (void)forwardContactPickerController:(TGForwardContactPickerController *)__unused contactPicker didSelectContact:(TGUser *)contactUser { [_companion controllerWantsToSendContact:contactUser asReplyToMessageId:[self currentReplyMessageId]]; } - (NSString *)_dictionaryString:(NSDictionary *)dict { NSMutableString *string = [[NSMutableString alloc] init]; [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, __unused BOOL *stop) { if ([key isKindOfClass:[NSString class]]) [string appendString:key]; else if ([key isKindOfClass:[NSNumber class]]) [string appendString:[key description]]; [string appendString:@":"]; if ([value isKindOfClass:[NSString class]]) [string appendString:value]; else if ([value isKindOfClass:[NSNumber class]]) [string appendString:[value description]]; else if ([value isKindOfClass:[NSDictionary class]]) { [string appendString:@"{"]; [string appendString:[self _dictionaryString:value]]; [string appendString:@"}"]; } [string appendString:@";"]; }]; return string; } - (bool)inputPanelAudioRecordingEnabled:(TGModernConversationInputTextPanel *)__unused inputTextPanel { __autoreleasing NSString *disabledMessage = nil; if (![TGApplicationFeatures isAudioUploadEnabledForPeerType:[_companion applicationFeaturePeerType] disabledMessage:&disabledMessage]) { [[[TGAlertView alloc] initWithTitle:TGLocalized(@"FeatureDisabled.Oops") message:disabledMessage cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; return false; } return true; } - (void)inputPanelAudioRecordingStart:(TGModernConversationInputTextPanel *)__unused inputTextPanel completion:(void (^)())completion { [self startAudioRecording:false completion:completion]; } - (void)inputPanelAudioRecordingCancel:(TGModernConversationInputTextPanel *)__unused inputTextPanel { [self stopAudioRecording]; [_inputTextPanel audioRecordingFinished]; [[UIApplication sharedApplication] setIdleTimerDisabled:false]; } - (void)inputPanelAudioRecordingComplete:(TGModernConversationInputTextPanel *)__unused inputTextPanel { #if TARGET_IPHONE_SIMULATOR [self finishAudioRecording:true]; return; #endif [self finishAudioRecording:false]; } - (void)startAudioRecording:(bool)speaker completion:(void (^)())completion { [self stopAudioRecording]; [self stopInlineMediaIfPlaying]; __weak TGModernConversationController *weakSelf = self; void (^block)() = ^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_currentAudioRecorder == nil) { strongSelf->_currentAudioRecorder = [[TGAudioRecorder alloc] initWithFileEncryption:[_companion encryptUploads]]; if (!speaker) { strongSelf->_currentAudioRecorder.micLevel = ^(CGFloat level){ TGDispatchOnMainThread(^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_inputTextPanel addMicLevel:level]; } }); }; } strongSelf->_currentAudioRecorderIsTouchInitiated = !speaker; strongSelf->_currentAudioRecorder.pauseRecording = ^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf finishAudioRecording:true]; } }; strongSelf->_currentAudioRecorder.delegate = self; strongSelf->_currentAudioRecorder.activityHolder = [strongSelf->_companion acquireAudioRecordingActivityHolder]; [strongSelf->_currentAudioRecorder startWithSpeaker:speaker completion:^{ if (completion) { TGDispatchOnMainThread(completion); } }]; [[UIApplication sharedApplication] setIdleTimerDisabled:true]; } [strongSelf updateRaiseToListen]; } }; if (TGTelegraphInstance.musicPlayer != nil) { [TGTelegraphInstance.musicPlayer controlPause:^{ TGDispatchOnMainThread(^{ block(); }); }]; } else { block(); } } - (void)finishAudioRecording:(bool)preview { dispatch_block_t block = ^{ if (_currentAudioRecorder != nil) { _currentAudioRecorder.delegate = nil; [_currentAudioRecorder finish:^(TGDataItem *dataItem, NSTimeInterval duration, TGLiveUploadActorData *liveData, TGAudioWaveform *waveform) { TGDispatchOnMainThread(^ { if (dataItem != nil) { if (preview) { [self previewAudioWithDataItem:dataItem duration:duration liveUploadData:liveData waveform:waveform]; } else { [_companion controllerWantsToSendLocalAudioWithDataItem:dataItem duration:duration liveData:liveData waveform:waveform asReplyToMessageId:[self currentReplyMessageId]]; } } else [_inputTextPanel shakeControls]; }); }]; _currentAudioRecorder = nil; _currentAudioRecorderIsTouchInitiated = false; [_inputTextPanel audioRecordingFinished]; if ([self shouldAutorotate]) [TGViewController attemptAutorotation]; [[UIApplication sharedApplication] setIdleTimerDisabled:false]; } [self updateRaiseToListen]; }; if (TGTelegraphInstance.musicPlayer != nil) { [TGTelegraphInstance.musicPlayer _dispatch:^{ TGDispatchOnMainThread(^{ block(); }); }]; } else { block(); } } - (NSTimeInterval)inputPanelAudioRecordingDuration:(TGModernConversationInputTextPanel *)__unused inputTextPanel { if (_currentAudioRecorder != nil) return [_currentAudioRecorder currentDuration]; return 0.0; } - (void)audioRecorderDidStartRecording:(TGAudioRecorder *)audioRecorder { TGDispatchOnMainThread(^ { if (audioRecorder == _currentAudioRecorder) [_inputTextPanel audioRecordingStarted]; }); } - (bool)inputPanelSendShouldBeAlwaysEnabled:(TGModernConversationInputTextPanel *)__unused inputTextPanel { return [self currentForwardMessages].count != 0; } - (TGViewController *)inputPanelParentViewController:(TGModernConversationInputTextPanel *)__unused inputTextPanel { return self; } - (void)inputPanelToggleBroadcastMode:(TGModernConversationInputTextPanel *)__unused inputTextPanel { [_companion _toggleBroadcastMode]; TGModernConversationController *strongSelf = self; [strongSelf->_tooltipContainerView removeFromSuperview]; strongSelf->_tooltipContainerView = nil; if ([strongSelf->_inputTextPanel broadcastModeButtonFrame].size.width > FLT_EPSILON) { strongSelf->_tooltipContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, strongSelf.view.frame.size.width, strongSelf.view.frame.size.height)]; [strongSelf.view addSubview:strongSelf->_tooltipContainerView]; NSMutableArray *actions = [[NSMutableArray alloc] init]; [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:!_inputTextPanel.isBroadcasting ? TGLocalized(@"Conversation.SilentBroadcastTooltipOff") : TGLocalized(@"Conversation.SilentBroadcastTooltipOn"), @"title", nil]]; [strongSelf->_tooltipContainerView.menuView setButtonsAndActions:actions watcherHandle:nil]; [strongSelf->_tooltipContainerView.menuView sizeToFit]; strongSelf->_tooltipContainerView.menuView.userInteractionEnabled = false; CGRect titleLockIconViewFrame = [strongSelf->_inputTextPanel convertRect:[strongSelf->_inputTextPanel broadcastModeButtonFrame] toView:strongSelf->_tooltipContainerView]; titleLockIconViewFrame.origin.y += 12.0f; [strongSelf->_tooltipContainerView showMenuFromRect:titleLockIconViewFrame animated:false]; } } - (void)_enterEditingMode:(int32_t)animateFromMessageId { if (!_editingMode) { [self setCurrentTitlePanel:nil animation:TGModernConversationPanelAnimationSlide]; [_companion clearCheckedMessages]; [_companion setMessageChecked:animateFromMessageId checked:true]; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *messageItem = cell.boundItem; [messageItem setTemporaryHighlighted:false viewStorage:_viewStorage]; } _editingMode = true; NSArray *visibleCells = [_collectionView.visibleCells sortedArrayUsingComparator:^NSComparisonResult(TGModernCollectionCell *cell1, TGModernCollectionCell *cell2) { return cell1.frame.origin.y > cell2.frame.origin.y ? NSOrderedAscending : NSOrderedDescending; }]; _companion.viewContext.editing = true; NSUInteger animateFromIndex = NSNotFound; for (NSUInteger i = 0; i < visibleCells.count; i++) { TGMessageModernConversationItem * item = ((TGModernCollectionCell *)visibleCells[i]).boundItem; if (item != nil && item->_message.mid == animateFromMessageId) { animateFromIndex = i; break; } } if (false && animateFromIndex != NSNotFound) { [(TGMessageModernConversationItem *)((TGModernCollectionCell *)visibleCells[animateFromIndex]).boundItem updateEditingState:_viewStorage animationDelay:0.0]; NSTimeInterval upDelay = 0.01; for (int i = animateFromIndex + 1; i < (int)visibleCells.count; i++) { TGModernCollectionCell *cell = visibleCells[i]; [(TGMessageModernConversationItem *)cell.boundItem updateEditingState:_viewStorage animationDelay:upDelay]; upDelay += 0.008; } NSTimeInterval downDelay = 0.01; for (int i = animateFromIndex - 1; i >= 0; i--) { TGModernCollectionCell *cell = visibleCells[i]; [(TGMessageModernConversationItem *)cell.boundItem updateEditingState:_viewStorage animationDelay:downDelay]; downDelay += 0.008; } } else { NSTimeInterval delay = 0.0; for (TGModernCollectionCell *cell in visibleCells) { [(TGMessageModernConversationItem *)cell.boundItem updateEditingState:_viewStorage animationDelay:delay]; delay += 0.006; } } [self setLeftBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:[_companion canDeleteAllMessages] ? TGLocalized(@"Conversation.ClearAll") : @"" style:UIBarButtonItemStylePlain target:self action:@selector(clearAllButtonPressed)] animated:true]; [self setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(doneButtonPressed)] animated:true]; TGModernConversationEditingPanel *editPanel = [[TGModernConversationEditingPanel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _view.frame.size.width, 0.0f)]; editPanel.delegate = self; [editPanel setForwardingEnabled:[_companion allowMessageForwarding]]; [editPanel setDeleteEnabled:[self canDeleteSelectedMessages]]; [self setInputPanel:editPanel animated:true]; [self _updateEditingPanel]; [_titleView setEditingMode:true animated:true]; } } - (bool)canDeleteSelectedMessages { if ([_companion canDeleteMessages]) { return true; } NSArray *checkedMessageIds = [_companion checkedMessageIds]; for (TGMessageModernConversationItem *item in _items) { int32_t mid = item->_message.mid; for (NSNumber *nMid in checkedMessageIds) { if ([nMid intValue] == mid) { if (![_companion canDeleteMessage:item->_message]) { return false; } break; } } } return true; } static UIView *_findBackArrow(UIView *view) { static Class backArrowClass = NSClassFromString(TGEncodeText(@"`VJObwjhbujpoCbsCbdlJoejdbupsWjfx", -1)); if ([view isKindOfClass:backArrowClass]) return view; for (UIView *subview in view.subviews) { UIView *result = _findBackArrow(subview); if (result != nil) return result; } return nil; } - (void)_leaveEditingModeAnimated:(bool)animated { if (_editingMode) { [_companion clearCheckedMessages]; _editingMode = false; NSArray *visibleCells = [_collectionView.visibleCells sortedArrayUsingComparator:^NSComparisonResult(TGModernCollectionCell *cell1, TGModernCollectionCell *cell2) { return cell1.frame.origin.y > cell2.frame.origin.y ? NSOrderedAscending : NSOrderedDescending; }]; _companion.viewContext.editing = false; NSTimeInterval delay = 0.0; for (TGModernCollectionCell *cell in visibleCells) { [(TGMessageModernConversationItem *)cell.boundItem updateEditingState:_viewStorage animationDelay:delay]; delay += 0.006; } [self setLeftBarButtonItem:[self defaultLeftBarButtonItem] animated:animated]; [self setRightBarButtonItem:[self defaultRightBarButtonItem] animated:animated]; [self setInputPanel:_customInputPanel != nil ? _customInputPanel : [self defaultInputPanel] animated:animated]; if (animated && iosMajorVersion() >= 7) { UIView *backArrow = _findBackArrow(self.navigationController.navigationBar); backArrow.alpha = 0.0f; [UIView animateWithDuration:0.3 delay:0.17 options:0 animations:^ { backArrow.alpha = 1.0f; } completion:nil]; } [_titleView setEditingMode:false animated:animated]; } } - (void)clearAllButtonPressed { if ([_companion canDeleteAllMessages]) { ASHandle *actionHandle = _actionHandle; [[[TGAlertView alloc] initWithTitle:nil message:TGLocalized(@"Conversation.ClearAllConfirmation") cancelButtonTitle:TGLocalized(@"Common.Cancel") okButtonTitle:TGLocalized(@"Common.OK") completionBlock:^(bool okButtonPressed) { if (okButtonPressed) [actionHandle requestAction:@"clearAllMessages" options:nil]; }] show]; } } - (void)_commitClearAllMessages { [self _leaveEditingModeAnimated:true]; [_companion controllerClearedConversation]; } - (void)_commitDeleteCheckedMessages { NSArray *checkedMessageIds = [_companion checkedMessageIds]; std::set messageIds; for (NSNumber *nMid in checkedMessageIds) { messageIds.insert([nMid int32Value]); } NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; int index = -1; for (TGMessageModernConversationItem *messageItem in _items) { index++; if (messageIds.find(messageItem->_message.mid) != messageIds.end()) { [indexSet addIndex:index]; } } //[self _deleteItemsAtIndices:indexSet animated:true animationFactor:1.0f]; //[self _leaveEditingModeAnimated:true]; [_companion _deleteMessages:checkedMessageIds animated:true]; __weak TGModernConversationController *weakSelf = self; [_companion controllerDeletedMessages:checkedMessageIds completion:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf _leaveEditingModeAnimated:true]; } }]; } - (void)doneButtonPressed { [self _leaveEditingModeAnimated:true]; } - (void)unseenMessagesButtonPressed { if (_scrollBackMessageId != 0) { int32_t messageId = _scrollBackMessageId; _scrollBackMessageId = 0; _hasUnseenMessagesBelow = false; [self setScrollBackButtonVisible:false]; [_companion navigateToMessageId:messageId scrollBackMessageId:0 animated:true]; } else { if (_enableBelowHistoryRequests) [_companion _performFastScrollDown:false]; else { if (_collectionView.contentOffset.y > -_collectionView.contentInset.top) { [_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true]; _scrollingToBottom = @true; } } } } #pragma mark - - (void)inputPanelWillChangeHeight:(TGModernConversationInputPanel *)inputPanel height:(CGFloat)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve { if (inputPanel == _currentInputPanel) { [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:_keyboardHeight duration:duration animationCurve:animationCurve]; [self _adjustCollectionViewForSize:_view.bounds.size keyboardHeight:_keyboardHeight inputContainerHeight:height duration:duration animationCurve:0]; } } - (void)_adjustCollectionViewForSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight inputContainerHeight:(CGFloat)inputContainerHeight duration:(NSTimeInterval)duration animationCurve:(int)animationCurve { [self _adjustCollectionViewForSize:size keyboardHeight:keyboardHeight inputContainerHeight:inputContainerHeight scrollToBottom:false duration:duration animationCurve:animationCurve]; } - (void)_adjustCollectionViewForSize:(CGSize)__unused size keyboardHeight:(CGFloat)keyboardHeight inputContainerHeight:(CGFloat)inputContainerHeight scrollToBottom:(bool)scrollToBottom duration:(NSTimeInterval)duration animationCurve:(int)animationCurve { bool stopScrolling = _collectionView.contentOffset.y >= -_collectionView.contentInset.top; CGFloat contentHeight = _collectionView.contentSize.height; UIEdgeInsets originalInset = _collectionView.contentInset; UIEdgeInsets inset = originalInset; inset.top = keyboardHeight + inputContainerHeight; if (_snapshotBackgroundView != nil) { CGRect snapshotBackgroundFrame = _snapshotBackgroundView.frame; snapshotBackgroundFrame.origin.y = -inset.top + 45.0f; _snapshotBackgroundView.frame = snapshotBackgroundFrame; } if (_snapshotImageView != nil) { CGRect snapshotImageFrame = _snapshotImageView.frame; snapshotImageFrame.origin.y = -inset.top + 45.0f; _snapshotImageView.frame = snapshotImageFrame; } CGPoint originalContentOffset = _collectionView.contentOffset; CGPoint contentOffset = originalContentOffset; if (scrollToBottom) contentOffset = CGPointMake(0.0f, -_collectionView.contentInset.top); else { contentOffset.y += originalInset.top - inset.top; contentOffset.y = MIN(contentOffset.y, contentHeight - _collectionView.bounds.size.height + inset.bottom); contentOffset.y = MAX(contentOffset.y, -inset.top); } if (stopScrolling) [_collectionView stopScrollingAnimation]; if (duration > DBL_EPSILON) { [UIView animateWithDuration:duration delay:0 options:(animationCurve << 16) | UIViewAnimationOptionBeginFromCurrentState animations:^ { //[_collectionView setDelayVisibleItemsUpdate:originalInset.top < inset.top && (iosMajorVersion() < 7 || iosMajorVersion() >= 8)]; bool decorationViewUpdatesWereDisabled = [_collectionView disableDecorationViewUpdates]; [_collectionView setDisableDecorationViewUpdates:decorationViewUpdatesWereDisabled || originalInset.top < inset.top]; _collectionView.contentInset = inset; if (!CGPointEqualToPoint(contentOffset, originalContentOffset)) { [_collectionView setBounds:CGRectMake(0, contentOffset.y, _collectionView.frame.size.width, _collectionView.frame.size.height)]; } [self _updateUnseenMessagesButton]; [_collectionView setDelayVisibleItemsUpdate:false]; [_collectionView setDisableDecorationViewUpdates:decorationViewUpdatesWereDisabled]; if (!decorationViewUpdatesWereDisabled) [_collectionView updateHeaderView]; [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset], 0.0f, _collectionView.contentInset.top, 0.0f) duration:0.0 curve:0]; } completion:^(BOOL finished) { if (finished) { [_collectionView updateVisibleItemsNow]; } }]; } else { _collectionView.contentInset = inset; if (!CGPointEqualToPoint(contentOffset, originalContentOffset)) [_collectionView setBounds:CGRectMake(0, contentOffset.y, _collectionView.frame.size.width, _collectionView.frame.size.height)]; [self _updateUnseenMessagesButton]; [_emptyListPlaceholder adjustLayoutForSize:_view.bounds.size contentInsets:UIEdgeInsetsMake(_collectionView == nil ? self.controllerInset.top : (_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset]), 0.0f, _collectionView == nil ? _currentInputPanel.frame.size.height : _collectionView.contentInset.top, 0.0f) duration:0.0 curve:0]; } } - (void)keyboardWillHide:(NSNotification *)__unused notification { } - (UIView *)keyboardView { UIView *keyboardWindow = [TGHacks applicationKeyboardWindow]; return keyboardWindow; } - (void)keyboardWillChangeFrame:(NSNotification *)notification { if (_collectionView == nil) return; CGSize collectionViewSize = _view.frame.size; NSTimeInterval duration = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] == nil ? 0.3 : [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; int curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue]; UIView *keyboardTransitionView = nil; if (_inputTextPanel.changingKeyboardMode) { keyboardTransitionView = [self keyboardView]; duration = 0.2; curve = 7; } CGRect screenKeyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGRect keyboardFrame = [_view convertRect:screenKeyboardFrame fromView:nil]; CGFloat keyboardHeight = (keyboardFrame.size.height <= FLT_EPSILON || keyboardFrame.size.width <= FLT_EPSILON) ? 0.0f : (collectionViewSize.height - keyboardFrame.origin.y); _halfTransitionKeyboardHeight = keyboardFrame.size.height; if ((freedomUIKitTest3() && freedomUIKitTest3_1()) || [self viewControllerIsAnimatingAppearanceTransition] || [(TGNavigationController *)self.navigationController isInPopTransition] || [(TGNavigationController *)self.navigationController isInControllerTransition]) return; if (_inputTextPanel.changingKeyboardMode && keyboardHeight < FLT_EPSILON) return; keyboardHeight = MAX(keyboardHeight, 0.0f); if (keyboardFrame.origin.y + keyboardFrame.size.height < collectionViewSize.height - FLT_EPSILON) keyboardHeight = 0.0f; if ( ABS(_keyboardHeight - keyboardHeight) < FLT_EPSILON && ABS(collectionViewSize.width - _collectionView.frame.size.width) < FLT_EPSILON) return; if (_inputTextPanel.changingKeyboardMode) { UIView *snapshotView = [keyboardTransitionView snapshotViewAfterScreenUpdates:false]; [[TGHacks applicationKeyboardWindow] addSubview:snapshotView]; CGFloat deltaHeight = keyboardHeight - _keyboardHeight; snapshotView.frame = CGRectOffset(snapshotView.frame, 0.0f, -deltaHeight); UIView *keyboardWindow = [TGHacks applicationKeyboardWindow]; keyboardWindow.frame = CGRectOffset(keyboardWindow.frame, 0.0f, deltaHeight); [UIView animateWithDuration:0.2 delay:0.0 options:curve << 16 animations:^ { snapshotView.alpha = 0.0f; keyboardWindow.frame = CGRectOffset(keyboardWindow.frame, 0.0f, -deltaHeight); } completion:^(__unused BOOL finished) { [snapshotView removeFromSuperview]; }]; if ([_inputTextPanel.maybeInputField.internalTextView.inputView isKindOfClass:[TGCommandKeyboardView class]]) { //[(TGCommandKeyboardView *)_inputTextPanel.maybeInputField.internalTextView.inputView animateTransitionIn]; } } if (ABS(_keyboardHeight - keyboardHeight) > FLT_EPSILON) { _keyboardHeight = keyboardHeight; if (ABS(collectionViewSize.width - _collectionView.frame.size.width) > FLT_EPSILON) { if (iosMajorVersion() >= 9) { [self _performSizeChangesWithDuration:0.3 size:_view.bounds.size]; } else { dispatch_async(dispatch_get_main_queue(), ^ { [self _performSizeChangesWithDuration:0.3 size:_view.bounds.size]; }); } } else { [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:keyboardHeight duration:duration animationCurve:curve]; [self _adjustCollectionViewForSize:_view.bounds.size keyboardHeight:keyboardHeight inputContainerHeight:_currentInputPanel.frame.size.height duration:duration animationCurve:curve]; } } } - (void)keyboardDidChangeFrame:(NSNotification *)notification { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad && notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] == nil) { CGSize collectionViewSize = _view.frame.size; NSTimeInterval duration = 0.3; int curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue]; CGRect screenKeyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGRect keyboardFrame = [_view convertRect:screenKeyboardFrame fromView:nil]; CGFloat keyboardHeight = (keyboardFrame.size.height <= FLT_EPSILON || keyboardFrame.size.width <= FLT_EPSILON) ? 0.0f : (collectionViewSize.height - keyboardFrame.origin.y); if (keyboardFrame.origin.y + keyboardFrame.size.height < collectionViewSize.height - FLT_EPSILON) keyboardHeight = 0.0f; _keyboardHeight = keyboardHeight; [_currentInputPanel adjustForSize:_view.bounds.size keyboardHeight:keyboardHeight duration:duration animationCurve:curve]; [self _adjustCollectionViewForSize:_view.bounds.size keyboardHeight:keyboardHeight inputContainerHeight:_currentInputPanel.frame.size.height duration:duration animationCurve:curve]; } } - (void)_performSizeChangesWithDuration:(NSTimeInterval)duration size:(CGSize)size { bool animated = duration > DBL_EPSILON; CGSize collectionViewSize = size; CGFloat keyboardHeight = _keyboardHeight; if (_titlePanelWrappingView != nil) { CGRect titleWrapperFrame = CGRectMake(0.0f, self.controllerInset.top, collectionViewSize.width, _titlePanelWrappingView.frame.size.height); CGRect titlePanelFrame = CGRectMake(0.0f, 0.0f, titleWrapperFrame.size.width, _currentTitlePanel.frame.size.height); if (duration > DBL_EPSILON) { [UIView animateWithDuration:duration animations:^ { _titlePanelWrappingView.frame = titleWrapperFrame; _currentTitlePanel.frame = titlePanelFrame; }]; } else { _titlePanelWrappingView.frame = titleWrapperFrame; _currentTitlePanel.frame = titlePanelFrame; } } [_currentInputPanel changeToSize:size keyboardHeight:keyboardHeight duration:duration]; CGFloat maxOriginY = _collectionView.contentOffset.y + _collectionView.contentInset.top; CGPoint previousContentOffset = _collectionView.contentOffset; CGRect previousCollectionFrame = _collectionView.frame; int anchorItemIndex = -1; CGFloat anchorItemOriginY = 0.0f; CGFloat anchorItemRelativeOffset = 0.0f; CGFloat anchorItemHeight = 0.0f; NSMutableArray *previousItemFrames = [[NSMutableArray alloc] init]; CGRect previousVisibleBounds = CGRectMake(previousContentOffset.x, previousContentOffset.y, _collectionView.frame.size.width, _collectionView.frame.size.height); NSMutableSet *previousVisibleItemIndices = [[NSMutableSet alloc] init]; std::vector previousDecorationAttributes; NSArray *previousLayoutAttributes = [_collectionLayout layoutAttributesForItems:_items containerWidth:previousCollectionFrame.size.width maxHeight:FLT_MAX decorationViewAttributes:&previousDecorationAttributes contentHeight:NULL]; int collectionItemCount = (int)_items.count; for (int i = 0; i < collectionItemCount; i++) { UICollectionViewLayoutAttributes *attributes = previousLayoutAttributes[i]; CGRect itemFrame = attributes.frame; if (itemFrame.origin.y < maxOriginY) { anchorItemHeight = itemFrame.size.height; anchorItemIndex = i; anchorItemOriginY = itemFrame.origin.y; } if (!CGRectIsEmpty(CGRectIntersection(itemFrame, previousVisibleBounds))) [previousVisibleItemIndices addObject:@(i)]; [previousItemFrames addObject:[NSValue valueWithCGRect:itemFrame]]; } if (anchorItemIndex != -1) { if (anchorItemHeight > 1.0f) anchorItemRelativeOffset = (anchorItemOriginY - (_collectionView.contentOffset.y + _collectionView.contentInset.top)) / anchorItemHeight; } _collectionView.frame = CGRectMake(0, -210.0f, collectionViewSize.width, collectionViewSize.height + 210.0f); [_companion _setControllerWidthForItemCalculation:_collectionView.frame.size.width]; [_collectionLayout invalidateLayout]; UIEdgeInsets originalInset = _collectionView.contentInset; UIEdgeInsets inset = originalInset; inset.top = keyboardHeight + _currentInputPanel.frame.size.height; inset.bottom = self.controllerInset.top + 210.0f + [_collectionView implicitTopInset]; _collectionView.contentInset = inset; [self _updateUnseenMessagesButton]; [_emptyListPlaceholder adjustLayoutForSize:size contentInsets:UIEdgeInsetsMake(_collectionView.contentInset.bottom - 210.0f - [_collectionView implicitTopInset], 0.0f, _collectionView.contentInset.top, 0.0f) duration:duration curve:0]; CGFloat newContentHeight = 0.0f; std::vector newDecorationAttributes; NSArray *newLayoutAttributes = [_collectionLayout layoutAttributesForItems:_items containerWidth:_collectionView.frame.size.width maxHeight:FLT_MAX decorationViewAttributes:&newDecorationAttributes contentHeight:&newContentHeight]; CGPoint newContentOffset = _collectionView.contentOffset; newContentOffset.y = - _collectionView.contentInset.top; if (anchorItemIndex >= 0 && anchorItemIndex < (int)newLayoutAttributes.count) { UICollectionViewLayoutAttributes *attributes = newLayoutAttributes[anchorItemIndex]; newContentOffset.y += attributes.frame.origin.y - CGFloor(anchorItemRelativeOffset * attributes.frame.size.height); } if (newContentOffset.y > newContentHeight + _collectionView.contentInset.bottom - _collectionView.frame.size.height) newContentOffset.y = newContentHeight + _collectionView.contentInset.bottom - _collectionView.frame.size.height; if (newContentOffset.y < -_collectionView.contentInset.top) newContentOffset.y = -_collectionView.contentInset.top; NSMutableArray *transitiveItemIndicesWithFrames = [[NSMutableArray alloc] init]; CGRect newVisibleBounds = CGRectMake(newContentOffset.x, newContentOffset.y, _collectionView.frame.size.width, _collectionView.frame.size.height); for (int i = 0; i < collectionItemCount; i++) { UICollectionViewLayoutAttributes *attributes = newLayoutAttributes[i]; CGRect itemFrame = attributes.frame; if (CGRectIsEmpty(CGRectIntersection(itemFrame, newVisibleBounds)) && [previousVisibleItemIndices containsObject:@(i)]) [transitiveItemIndicesWithFrames addObject:@[@(i), [NSValue valueWithCGRect:itemFrame]]]; } NSMutableDictionary *transitiveCells = [[NSMutableDictionary alloc] init]; if (animated && !_collectionView.decelerating && !_collectionView.dragging && !_collectionView.tracking) { for (NSArray *nDesc in transitiveItemIndicesWithFrames) { NSNumber *nIndex = nDesc[0]; TGModernCollectionCell *currentCell = (TGModernCollectionCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:[nIndex intValue] inSection:0]]; if (currentCell != nil) { TGModernCollectionCell *transitiveCell = [[TGModernCollectionCell alloc] initWithFrame:[nDesc[1] CGRectValue]]; [(TGModernConversationItem *)_items[[nIndex intValue]] moveToCell:transitiveCell]; transitiveCells[nIndex] = transitiveCell; } } } _collectionView.contentOffset = newContentOffset; [_collectionView updateVisibleItemsNow]; [_collectionView layoutSubviews]; if (animated) { _collectionView.clipsToBounds = false; CGFloat contentOffsetDifference = newContentOffset.y - previousContentOffset.y + (_collectionView.frame.size.height - previousCollectionFrame.size.height); CGFloat widthDifference = _collectionView.frame.size.width - previousCollectionFrame.size.width; NSMutableArray *itemFramesToRestore = [[NSMutableArray alloc] init]; bool contentUpdatesWereDisabled = _companion.viewContext.contentUpdatesDisabled; _companion.viewContext.contentUpdatesDisabled = true; for (int i = 0; i < collectionItemCount; i++) { TGModernCollectionCell *cell = (TGModernCollectionCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; TGModernCollectionCell *transitiveCell = transitiveCells[@(i)]; if (transitiveCell != nil) { if (cell == nil) { cell = transitiveCell; [_collectionView addSubview:transitiveCell]; } else { if ([_items[i] boundCell] == transitiveCell) [_items[i] moveToCell:cell]; [transitiveCells removeObjectForKey:@(i)]; } } if (cell != nil) { [itemFramesToRestore addObject:@[@(i), [NSValue valueWithCGRect:cell.frame]]]; CGRect previousFrame = [previousItemFrames[i] CGRectValue]; cell.frame = CGRectOffset(previousFrame, widthDifference, contentOffsetDifference); TGModernConversationItem *item = _items[i]; [item sizeForContainerSize:CGSizeMake(previousFrame.size.width, 0.0f)]; } } for (auto it = previousDecorationAttributes.begin(); it != previousDecorationAttributes.end(); it++) { UIView *decorationView = [_collectionView viewForDecorationAtIndex:it->index]; decorationView.frame = CGRectOffset(it->frame, widthDifference, contentOffsetDifference); [decorationView layoutSubviews]; } [UIView animateWithDuration:duration animations:^ { for (NSArray *frameDesc in itemFramesToRestore) { UIView *cell = [_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:[frameDesc[0] intValue] inSection:0]]; if (cell == nil) cell = transitiveCells[frameDesc[0]]; cell.frame = [frameDesc[1] CGRectValue]; TGModernConversationItem *item = _items[[frameDesc[0] intValue]]; [item sizeForContainerSize:CGSizeMake(_collectionView.frame.size.width, 0.0f)]; } for (auto it = newDecorationAttributes.begin(); it != newDecorationAttributes.end(); it++) { UIView *decorationView = [_collectionView viewForDecorationAtIndex:it->index]; decorationView.frame = it->frame; [decorationView layoutSubviews]; } } completion:^(BOOL finished) { if (finished) { _collectionView.clipsToBounds = true; } [transitiveCells enumerateKeysAndObjectsUsingBlock:^(NSNumber *nIndex, TGModernCollectionCell *cell, __unused BOOL *stop) { [(TGModernConversationItem *)_items[[nIndex intValue]] unbindCell:_viewStorage]; [cell removeFromSuperview]; }]; }]; _companion.viewContext.contentUpdatesDisabled = contentUpdatesWereDisabled; [_collectionView updateRelativeBounds]; } } #pragma mark - - (void)actionStageActionRequested:(NSString *)action options:(id)options { if ([action isEqualToString:@"clearAllMessages"]) { [self _commitClearAllMessages]; } else if ([action isEqualToString:@"mapViewFinished"]) { [self dismissViewControllerAnimated:true completion:nil]; if (options[@"latitude"] != nil) { [_companion controllerWantsToSendMapWithLatitude:[options[@"latitude"] doubleValue] longitude:[options[@"longitude"] doubleValue] venue:nil asReplyToMessageId:[self currentReplyMessageId]]; } } else if ([action isEqualToString:@"menuAction"]) { int32_t mid = [options[@"userInfo"][@"mid"] int32Value]; if (mid != 0) { TGMessageModernConversationItem *menuMessageItem = nil; int index = -1; for (TGMessageModernConversationItem *messageItem in _items) { index++; if (messageItem->_message.mid == mid) { menuMessageItem = messageItem; break; } } NSString *menuAction = options[@"action"]; if ([menuAction isEqualToString:@"copy"]) { if (menuMessageItem != nil) { NSString *text = nil; if (menuMessageItem->_message.text.length != 0) { text = menuMessageItem->_message.text; } else { for (TGMediaAttachment *attachment in menuMessageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { TGImageMediaAttachment *imageAttachment = (TGImageMediaAttachment *)attachment; if (imageAttachment.caption.length != 0) text = imageAttachment.caption; } } } if (text.length > 0) { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; [pasteboard setString:text]; } } } else if ([menuAction isEqualToString:@"saveGif"]) { if (menuMessageItem != nil) { for (id attachment in menuMessageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { if ([(TGDocumentMediaAttachment *)attachment isAnimated]) { [TGRecentGifsSignal addRecentGifFromDocument:attachment]; } break; } } } } else if ([menuAction isEqualToString:@"delete"]) { if (menuMessageItem != nil && index >= 0) { [_companion controllerDeletedMessages:@[@(mid)] completion:nil]; } } else if ([menuAction isEqualToString:@"reply"]) { if (menuMessageItem != nil) { _inputTextPanel.inputField.internalTextView.enableFirstResponder = true; [self setReplyMessage:menuMessageItem->_message animated:true]; if (_currentInputPanel == _inputTextPanel && menuMessageItem->_message.replyMarkup.rows.count == 0) [self openKeyboard]; } } else if ([menuAction isEqualToString:@"edit"]) { [self endMessageEditing:false]; if (menuMessageItem != nil) { NSString *messageText = menuMessageItem->_message.text; for (id attachment in menuMessageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { messageText = ((TGImageMediaAttachment *)attachment).caption; } else if ([attachment isKindOfClass:[TGVideoMediaAttachment class]]) { messageText = ((TGVideoMediaAttachment *)attachment).caption; } } if (_editingContextDisposable == nil) { _editingContextDisposable = [[SMetaDisposable alloc] init]; } TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init]; [progressWindow showWithDelay:0.2]; __weak TGModernConversationController *weakSelf = self; [_editingContextDisposable setDisposable:[[[[[[_currentEditingMessageContext.signal take:1] timeout:5.0 onQueue:[SQueue concurrentDefaultQueue] orSignal:[SSignal fail:@"timeout"]] deliverOn:[SQueue mainQueue]] onDispose:^{ TGDispatchOnMainThread(^{ [progressWindow dismiss:true]; }); }] mapToSignal:^SSignal *(id next) { if (next == nil) { return [SSignal fail:nil]; } else { return [SSignal single:next]; } }] startWithNext:nil error:^(__unused id error) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { NSString *errorText = TGLocalized(@"Login.UnknownError"); if (![error isEqual:@"timeout"]) { errorText = TGLocalized(@"Channel.EditMessageErrorGeneric"); } [[[TGAlertView alloc] initWithTitle:nil message:errorText cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show]; } } completed:^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { bool isCaption = false; for (id attachment in menuMessageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]] || [attachment isKindOfClass:[TGVideoMediaAttachment class]]) { isCaption = true; break; } } [self setEditMessageWithText:messageText isCaption:isCaption messageId:menuMessageItem->_message.mid animated:true]; if (_currentInputPanel == _inputTextPanel && menuMessageItem->_message.replyMarkup.rows.count == 0) { [self openKeyboard]; } } }]]; } } else if ([menuAction isEqualToString:@"forward"]) [self forwardMessages:@[@(mid)] fastForward:false]; else if ([menuAction isEqualToString:@"stickerPackInfo"]) { id packReference = nil; for (id attachment in menuMessageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { for (id attribute in ((TGDocumentMediaAttachment *)attachment).attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) { packReference = ((TGDocumentAttributeSticker *)attribute).packReference; } } break; } } if (packReference != nil) { TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init]; [progressWindow show:true]; SSignal *stickerPackInfo = [TGStickersSignals stickerPackInfo:packReference]; SSignal *currentStickerPacks = [[TGStickersSignals stickerPacks] take:1]; SSignal *combinedSignal = [SSignal combineSignals:@[stickerPackInfo, currentStickerPacks]]; [[[combinedSignal deliverOn:[SQueue mainQueue]] onDispose:^ { TGDispatchOnMainThread(^ { [progressWindow dismiss:true]; }); }] startWithNext:^(NSArray *combined) { [TGAppDelegateInstance previewStickerPack:combined[0] currentStickerPacks:combined[1][@"packs"]]; } error:^(__unused id error) { } completed:nil]; } } else if ([menuAction isEqualToString:@"select"]) [self _enterEditingMode:mid]; else if ([menuAction isEqualToString:@"share"]) { if (menuMessageItem != nil) { NSURL *fileUrl = nil; for (id attachment in menuMessageItem->_message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { NSString *localFilePath = [[_companion fileUrlForDocumentMedia:attachment] path]; if ([[NSFileManager defaultManager] fileExistsAtPath:localFilePath isDirectory:NULL]) fileUrl = [_companion fileUrlForDocumentMedia:attachment]; break; } } if (fileUrl != nil) { CGRect messageRect = CGRectZero; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *messageItem = cell.boundItem; if (messageItem != nil && messageItem->_message.mid == mid) { CGRect contentFrame = [[cell contentViewForBinding] convertRect:[messageItem effectiveContentFrame] toView:_view]; if (CGRectIsNull(contentFrame) || CGRectIsEmpty(contentFrame)) break; contentFrame = CGRectIntersection(contentFrame, CGRectMake(0, 0, _view.frame.size.width, _currentInputPanel.frame.origin.y)); if (CGRectIsNull(contentFrame) || CGRectIsEmpty(contentFrame)) break; messageRect = contentFrame; } } _interactionController = [UIDocumentInteractionController interactionControllerWithURL:fileUrl]; _interactionController.delegate = self; _interactionController.UTI = [self utiForFileExtension:[[fileUrl pathExtension] lowercaseString]]; [_interactionController presentOptionsMenuFromRect:messageRect inView:_view animated:true]; } } } } } else if ([action isEqualToString:@"menuWillHide"]) { for (TGModernCollectionCell *cell in _collectionView.visibleCells) { TGMessageModernConversationItem *messageItem = cell.boundItem; [messageItem setTemporaryHighlighted:false viewStorage:_viewStorage]; } } } - (NSString *)utiForFileExtension:(NSString *)extension { if ([extension isEqualToString:@"jpg"] || [extension isEqualToString:@"jpeg"]) return @"public.jpeg"; else if ([extension isEqualToString:@"png"]) return @"public.png"; return @"public.data"; } - (void)userActivityWasContinued:(NSUserActivity *)userActivity { TGDispatchOnMainThread(^ { if (userActivity == _currentActivity) { } }); } - (void)userActivity:(NSUserActivity *)userActivity didReceiveInputStream:(NSInputStream *)__unused inputStream outputStream:(NSOutputStream *)__unused outputStream { TGDispatchOnMainThread(^ { if (userActivity == _currentActivity) { [self setInputText:@"" replace:true selectRange:NSMakeRange(0, 0)]; } }); } - (void)openEmbed:(TGWebPageMediaAttachment *)webPage { if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { return; } [self.view endEditing:true]; __weak TGModernConversationController *weakSelf = self; _attachmentSheetWindow = [[TGAttachmentSheetWindow alloc] init]; _attachmentSheetWindow.dismissalBlock = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; strongSelf->_attachmentSheetWindow.rootViewController = nil; strongSelf->_attachmentSheetWindow = nil; [strongSelf updateRaiseToListen]; }; [self updateRaiseToListen]; NSMutableArray *items = [[NSMutableArray alloc] init]; TGAttachmentSheetEmbedItemView *embedView = [[TGAttachmentSheetEmbedItemView alloc] initWithWebPage:webPage]; [items addObject:embedView]; [items addObject:[[TGAttachmentSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Web.OpenExternal") pressed:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_attachmentSheetWindow dismissAnimated:true completion:nil]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:webPage.url]]; } }]]; [items addObject:[[TGAttachmentSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Web.CopyLink") pressed:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_attachmentSheetWindow dismissAnimated:true completion:nil]; [[UIPasteboard generalPasteboard] setString:webPage.url]; } }]]; [items addObject:[[TGAttachmentSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") pressed:^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_attachmentSheetWindow dismissAnimated:true completion:nil]; } }]]; _attachmentSheetWindow.view.items = items; _attachmentSheetWindow.windowLevel = UIWindowLevelNormal; [_attachmentSheetWindow showAnimated:true completion:nil]; /*TGEmbedPreviewController *embedController = [[TGEmbedPreviewController alloc] initWithWebPage:webPage]; TGOverlayControllerWindow *window = [[TGOverlayControllerWindow alloc] initWithParentController:self contentController:embedController]; window.windowLevel = UIWindowLevelNormal; window.hidden = false; [self.view endEditing:true];*/ } - (void)hideKeyboard { [_inputTextPanel.maybeInputField resignFirstResponder]; } - (void)activateSearch { if (_searchBar == nil) { _searchBar = [[TGSearchBar alloc] initWithFrame:CGRectMake(0.0f, 20.0f, _view.frame.size.width, [TGSearchBar searchBarBaseHeight]) style:TGSearchBarStyleLight]; _searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; _searchBar.delegate = self; [_searchBar setShowsCancelButton:true animated:false]; [_searchBar setAlwaysExtended:true]; _searchBar.placeholder = TGLocalized(@"Conversation.SearchPlaceholder"); [_searchBar sizeToFit]; _searchBar.delayActivity = false; [_view insertSubview:_searchBar aboveSubview:_collectionView]; __weak TGModernConversationController *weakSelf = self; _searchPanel = [[TGModernConversationSearchInputPanel alloc] init]; _searchPanel.next = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_searchResultsOffset + 1 < strongSelf->_searchResultsIds.count) [strongSelf setSearchResultsOffset:strongSelf->_searchResultsOffset + 1]; } }; _searchPanel.previous = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (strongSelf->_searchResultsIds.count != 0 && strongSelf->_searchResultsOffset > 0) [strongSelf setSearchResultsOffset:strongSelf->_searchResultsOffset - 1]; } }; _searchPanel.done = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) [strongSelf searchBarCancelButtonClicked:(UISearchBar *)strongSelf->_searchBar]; }; _searchPanel.delegate = self; } _searchBar.hidden = false; [_searchPanel setInProgress:false]; [_searchPanel setOffset:0 count:0]; [self setCurrentTitlePanel:nil animation:TGModernConversationPanelAnimationSlideFar]; [self setNavigationBarHidden:true withAnimation:TGViewControllerNavigationBarAnimationSlide]; [self setCustomInputPanel:_searchPanel]; [_searchBar becomeFirstResponder]; } - (void)searchBar:(TGSearchBar *)__unused searchBar willChangeHeight:(CGFloat)__unused newHeight { } - (void)searchBarCancelButtonClicked:(UISearchBar *)__unused searchBar { _companion.viewContext.searchText = nil; for (TGMessageModernConversationItem *item in _items) { [item updateSearchText:true]; } [_searchBar resignFirstResponder]; [self setNavigationBarHidden:false withAnimation:TGViewControllerNavigationBarAnimationSlide]; TGDispatchAfter(0.3, dispatch_get_main_queue(), ^ { _searchBar.hidden = true; [_searchBar setText:@""]; }); [_searchPanel setInProgress:false]; [self setCustomInputPanel:nil]; } - (void)beginSearchWithQuery:(NSString *)query { if (_searchDisposable == nil) _searchDisposable = [[SMetaDisposable alloc] init]; _companion.viewContext.searchText = query.length == 0 ? nil : query; for (TGMessageModernConversationItem *item in _items) { [item updateSearchText:false]; } _query = query; if (query.length == 0) { [_searchDisposable setDisposable:nil]; [self setSearchResultsIds:nil]; [_searchPanel setInProgress:false]; } else { __weak TGModernConversationController *weakSelf = self; _searchBar.showActivity = true; [_searchPanel setInProgress:true]; [_searchDisposable setDisposable:[[[[TGGlobalMessageSearchSignals searchMessages:query peerId:((TGGenericModernConversationCompanion *)_companion).conversationId accessHash:[_companion requestAccessHash] itemMapping:^id(id item) { if ([item isKindOfClass:[TGConversation class]]) { TGConversation *conversation = item; return conversation; } return nil; }] deliverOn:[SQueue mainQueue]] onDispose:^ { TGDispatchOnMainThread(^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { strongSelf->_searchBar.showActivity = false; [strongSelf->_searchPanel setInProgress:false]; } }); }] startWithNext:^(id next) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { NSMutableArray *searchResultsIds = [[NSMutableArray alloc] init]; for (TGConversation *conversation in next) { if (conversation.additionalProperties[@"searchMessageId"] != nil) { [searchResultsIds addObject:conversation.additionalProperties[@"searchMessageId"]]; } } [strongSelf setSearchResultsIds:searchResultsIds]; } } error:^(__unused id error) { } completed:^ { }]]; } _searchPanel.isSearching = query.length != 0; } - (void)searchBar:(UISearchBar *)__unused searchBar textDidChange:(NSString *)searchText { [self beginSearchWithQuery:searchText]; } - (void)setSearchResultsIds:(NSArray *)searchResultIds { bool previousHadResults = _searchResultsIds.count != 0; NSNumber *previousId = nil; if (_searchResultsIds.count != 0) previousId = _searchResultsIds[_searchResultsOffset]; _searchResultsIds = searchResultIds; NSMutableSet *idsSet = [[NSMutableSet alloc] initWithArray:searchResultIds]; NSUInteger offset = 0; if (_searchResultsIds.count != 0) { if (!previousHadResults) offset = 0; else if (previousId != nil && [idsSet containsObject:previousId]) offset = [_searchResultsIds indexOfObject:previousId]; else { NSArray *visibleCells = [_collectionView.visibleCells sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) { return view1.frame.origin.y > view2.frame.origin.y ? NSOrderedAscending : NSOrderedDescending; }]; for (NSInteger topIndex = visibleCells.count / 2, bottomIndex = visibleCells.count / 2 + 1; topIndex >= 0 || bottomIndex < (NSInteger)visibleCells.count; topIndex--, bottomIndex++) { if (topIndex >= 0) { TGMessageModernConversationItem *item = ((TGMessageModernConversationItem *)((TGModernCollectionCell *)visibleCells[topIndex]).boundItem); if (item != nil) { NSNumber *nMid = @(item->_message.mid); if ([idsSet containsObject:nMid]) { offset = [searchResultIds indexOfObject:nMid]; break; } } } if (bottomIndex < (NSInteger)visibleCells.count) { TGMessageModernConversationItem *item = ((TGMessageModernConversationItem *)((TGModernCollectionCell *)visibleCells[bottomIndex]).boundItem); if (item != nil) { NSNumber *nMid = @(item->_message.mid); if ([idsSet containsObject:nMid]) { offset = [searchResultIds indexOfObject:nMid]; break; } } } } } } [self setSearchResultsOffset:offset]; } - (void)setSearchResultsOffset:(NSUInteger)searchResultsOffset { _searchResultsOffset = searchResultsOffset; if (_searchResultsIds.count != 0 && _searchResultsOffset < _searchResultsIds.count) { [_companion navigateToMessageId:[_searchResultsIds[_searchResultsOffset] intValue] scrollBackMessageId:0 animated:false]; } [_searchPanel setOffset:_searchResultsOffset count:_searchResultsIds.count]; } - (void)setInputDisabled:(bool)inputDisabled { _inputDisabled = inputDisabled; _inputTextPanel.inputDisabled = inputDisabled; } - (void)setIsChannel:(bool)isChannel { _isChannel = isChannel; _inputTextPanel.isChannel = isChannel; } - (void)updateControllerShouldHideInputTextByDefault { if (!_editingMode) { [self setInputPanel:_customInputPanel != nil ? _customInputPanel : [self defaultInputPanel] animated:ABS(CFAbsoluteTimeGetCurrent() - _willAppearTimestamp) > 0.18]; } } - (BOOL)isEditing { return _editingMode; } - (void)check3DTouch { if (iosMajorVersion() >= 9) { if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) { [self registerForPreviewingWithDelegate:(id)self sourceView:self.view]; } } } - (UIViewController *)previewingContext:(id)previewingContext viewControllerForLocation:(CGPoint)location { if (self.presentedViewController != nil) { return nil; } CGPoint collectionPoint = [self.view convertPoint:location toView:_collectionView]; for (TGModernCollectionCell *cell in _collectionView.visibleCells) { if (CGRectContainsPoint(cell.frame, collectionPoint)) { TGMessageModernConversationItem *item = cell.boundItem; if (item != nil) { NSString *link = [(TGMessageViewModel *)item.viewModel linkAtPoint:[_collectionView convertPoint:collectionPoint toView:[cell contentViewForBinding]]]; previewingContext.sourceRect = CGRectMake(location.x, location.y, 1.0f, 1.0f); if ([[link lowercaseString] hasPrefix:@"http://"] || [[link lowercaseString] hasPrefix:@"https://"] || [link rangeOfString:@"://"].location == NSNotFound) { NSURL *url = nil; @try { url = [NSURL URLWithString:link]; } @catch (NSException *e) {} if (url != nil) { SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:url]; return controller; } } } break; } } return nil; } - (void)previewingContext:(id)__unused previewingContext commitViewController:(UIViewController *)viewControllerToCommit { [self presentViewController:viewControllerToCommit animated:true completion:nil]; } - (NSArray> * _Nonnull)previewActionItems { __weak TGModernConversationController *weakSelf = self; int64_t peerId = ((TGGenericModernConversationCompanion *)_companion).conversationId; UIPreviewAction *thumbAction = [UIPreviewAction actionWithTitle:@"👍" style:UIPreviewActionStyleDefault handler:^(__unused UIPreviewAction * _Nonnull action, __unused UIViewController * _Nonnull previewViewController) { [[TGSendMessageSignals sendTextMessageWithPeerId:peerId text:@"👍" replyToMid:0] startWithNext:nil error: nil completed: ^ { }]; }]; int muteUntil = 0; [TGDatabaseInstance() loadPeerNotificationSettings:peerId soundId:NULL muteUntil:&muteUntil previewText:NULL messagesMuted:NULL notFound:NULL]; UIPreviewAction *muteAction = [UIPreviewAction actionWithTitle:TGPeerIdIsChannel(peerId) ? TGLocalized(@"Conversation.Mute") :TGLocalized(@"Notification.Mute1h") style:UIPreviewActionStyleDefault handler:^(__unused UIPreviewAction * _Nonnull action, __unused UIViewController * _Nonnull previewViewController) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { int muteUntil = 0; [TGDatabaseInstance() loadPeerNotificationSettings:peerId soundId:NULL muteUntil:&muteUntil previewText:NULL messagesMuted:NULL notFound:NULL]; int muteTime = TGPeerIdIsChannel(peerId) ? INT32_MAX : 1 * 60 * 60; muteUntil = TGPeerIdIsChannel(peerId) ? INT32_MAX : MAX(muteUntil, (int)[[TGTelegramNetworking instance] approximateRemoteTime] + muteTime); static int actionId = 0; void (^muteBlock)(int64_t, int32_t, NSNumber *) = ^(int64_t peerId, int32_t muteUntil, NSNumber *accessHash) { NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:@{ @"peerId": @(peerId), @"muteUntil": @(muteUntil) }]; if (accessHash != nil) options[@"accessHash"] = accessHash; [ActionStageInstance() requestActor:[NSString stringWithFormat:@"/tg/changePeerSettings/(%" PRId64 ")/(muteAction%d)", peerId, actionId++] options:options watcher:TGTelegraphInstance]; }; if (TGPeerIdIsChannel(peerId)) { [[[TGDatabaseInstance() existingChannel:peerId] take:1] startWithNext:^(TGConversation *channel) { muteBlock(peerId, muteUntil, @(channel.accessHash)); }]; } else { muteBlock(peerId, muteUntil, nil); } } }]; UIPreviewAction *unmuteAction = [UIPreviewAction actionWithTitle:TGLocalized(@"Conversation.Unmute") style:UIPreviewActionStyleDefault handler:^(__unused UIPreviewAction * _Nonnull action, __unused UIViewController * _Nonnull previewViewController) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { int muteUntil = 0; [TGDatabaseInstance() loadPeerNotificationSettings:peerId soundId:NULL muteUntil:&muteUntil previewText:NULL messagesMuted:NULL notFound:NULL]; muteUntil = 0; static int actionId = 0; void (^muteBlock)(int64_t, int32_t, NSNumber *) = ^(int64_t peerId, int32_t muteUntil, NSNumber *accessHash) { NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:@{ @"peerId": @(peerId), @"muteUntil": @(muteUntil) }]; if (accessHash != nil) options[@"accessHash"] = accessHash; [ActionStageInstance() requestActor:[NSString stringWithFormat:@"/tg/changePeerSettings/(%" PRId64 ")/(muteAction%d)", peerId, actionId++] options:options watcher:TGTelegraphInstance]; }; if (TGPeerIdIsChannel(peerId)) { [[[TGDatabaseInstance() existingChannel:peerId] take:1] startWithNext:^(TGConversation *channel) { muteBlock(peerId, muteUntil, @(channel.accessHash)); }]; } else { muteBlock(peerId, muteUntil, nil); } } }]; NSMutableArray *actions = [[NSMutableArray alloc] init]; if (!TGPeerIdIsChannel(peerId)) { [actions addObject:thumbAction]; } if (muteUntil <= [[TGTelegramNetworking instance] approximateRemoteTime]) { [actions addObject:muteAction]; } else { [actions addObject:unmuteAction]; } return actions; } - (void)forwardMessages:(NSArray *)messageIds fastForward:(bool)fastForward { [self.view endEditing:true]; if (fastForward) { __weak TGModernConversationController *weakSelf = self; _shareSheetWindow = [[TGShareSheetWindow alloc] init]; _shareSheetWindow.dismissalBlock = ^ { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf == nil) return; strongSelf->_shareSheetWindow.rootViewController = nil; strongSelf->_shareSheetWindow = nil; }; NSMutableArray *items = [[NSMutableArray alloc] init]; TGShareSheetSharePeersItemView *peersItemView = [[TGShareSheetSharePeersItemView alloc] init]; if ([_companion canCreateLinksToMessages]) { SVariable *sharedLink = [[SVariable alloc] init]; [sharedLink set:[[TGMessageSearchSignals shareLinkForChannelMessage:[_companion requestPeerId] accessHash:[_companion requestAccessHash] messageId:[messageIds.firstObject intValue]] catch:^SSignal *(__unused id error) { return [SSignal single:nil]; }]]; peersItemView.copyShareLink = ^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_shareSheetWindow dismissAnimated:true completion:nil]; if (messageIds.count != 0) { TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init]; [progressWindow showWithDelay:0.2]; [[[[sharedLink.signal take:1] deliverOn:[SQueue mainQueue]] onDispose:^{ [progressWindow dismiss:true]; }] startWithNext:^(id next) { if (next != nil) { [[UIPasteboard generalPasteboard] setString:next]; [progressWindow dismissWithSuccess]; } }]; } } }; } peersItemView.shareWithCaption = ^(NSArray *peerIds, NSString *caption) { __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf->_shareSheetWindow dismissAnimated:true completion:nil]; [strongSelf broadcastForwardMessages:messageIds caption:caption toPeerIds:peerIds]; [strongSelf _leaveEditingModeAnimated:true]; } }; [items addObject:peersItemView]; _shareSheetWindow.view.cancel = ^{ __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) [strongSelf->_shareSheetWindow dismissAnimated:true completion:nil]; }; _shareSheetWindow.view.items = items; _shareSheetWindow.windowLevel = UIWindowLevelNormal; [_shareSheetWindow showAnimated:true completion:nil]; } else { [_companion controllerWantsToForwardMessages:messageIds]; } } - (void)broadcastForwardMessages:(NSArray *)messageIds caption:(NSString *)caption toPeerIds:(NSArray *)peerIds { SSignal *signal = [TGSendMessageSignals forwardMessagesWithMessageIds:messageIds toPeerIds:peerIds fromPeerId:[_companion requestPeerId] fromPeerAccessHash:[_companion requestAccessHash]]; if (caption.length != 0) { signal = [[TGSendMessageSignals broadcastMessageWithText:caption toPeerIds:peerIds] then:signal]; } [signal startWithNext:nil]; } - (void)setLoadingMessages:(bool)loadingMessages { _loadingMessages = loadingMessages; if (loadingMessages) { if (_loadingMessagesIndicator == nil) { _loadingMessagesIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; _loadingMessagesIndicator.frame = CGRectMake(CGFloor((self.view.frame.size.width - _loadingMessagesIndicator.frame.size.width) / 2.0f), CGFloor((self.view.frame.size.height - _loadingMessagesIndicator.frame.size.height) / 2.0f), _loadingMessagesIndicator.frame.size.width, _loadingMessagesIndicator.frame.size.height); _loadingMessagesIndicator.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; [self.view addSubview:_loadingMessagesIndicator]; [_loadingMessagesIndicator startAnimating]; } _snapshotImageView.hidden = true; _collectionView.hidden = true; _snapshotBackgroundView.hidden = true; } else { _snapshotImageView.hidden = false; _collectionView.hidden = false; _snapshotBackgroundView.hidden = false; if (_loadingMessagesIndicator != nil) { [_loadingMessagesIndicator stopAnimating]; [_loadingMessagesIndicator removeFromSuperview]; _loadingMessagesIndicator = nil; } } } - (void)maybeDisplayGifTooltip { if (iosMajorVersion() < 8 || TGIsPad()) { return; } NSString *key = @"TG_displayedGifsTooltip_v0"; __block bool displayed = [[[NSUserDefaults standardUserDefaults] objectForKey:key] boolValue]; if (!displayed) { __weak TGModernConversationController *weakSelf = self; if (_recentGifsDisposable == nil) { _recentGifsDisposable = [[SMetaDisposable alloc] init]; } [_recentGifsDisposable setDisposable:[[[TGRecentGifsSignal recentGifs] deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *recentGifs) { if (displayed) { return; } __strong TGModernConversationController *strongSelf = weakSelf; if (strongSelf != nil) { if (recentGifs.count != 0) { [[NSUserDefaults standardUserDefaults] setObject:@true forKey:key]; displayed = true; if (strongSelf->_tooltipContainerView == nil && [strongSelf->_inputTextPanel stickerButtonFrame].size.width > FLT_EPSILON) { strongSelf->_tooltipContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, strongSelf.view.frame.size.width, strongSelf.view.frame.size.height)]; [strongSelf.view addSubview:strongSelf->_tooltipContainerView]; NSMutableArray *actions = [[NSMutableArray alloc] init]; [actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"Conversation.GifTooltip"), @"title", nil]]; [strongSelf->_tooltipContainerView.menuView setButtonsAndActions:actions watcherHandle:nil]; [strongSelf->_tooltipContainerView.menuView sizeToFit]; strongSelf->_tooltipContainerView.menuView.userInteractionEnabled = false; CGRect titleLockIconViewFrame = [strongSelf->_inputTextPanel convertRect:[strongSelf->_inputTextPanel stickerButtonFrame] toView:strongSelf->_tooltipContainerView]; titleLockIconViewFrame.origin.y += 12.0f; [strongSelf->_tooltipContainerView showMenuFromRect:titleLockIconViewFrame animated:false]; } } } }]]; } } - (void)setPrimaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)panel animated:(bool)animated skipHeightAnimation:(bool)skipHeightAnimation { [_inputTextPanel setPrimaryExtendedPanel:panel animated:animated skipHeightAnimation:skipHeightAnimation]; if (_currentInputPanel != _inputTextPanel && _currentInputPanel != nil && [_currentInputPanel respondsToSelector:@selector(setPrimaryExtendedPanel:animated:skipHeightAnimation:)]) { if ([panel isKindOfClass:[TGModernConversationForwardInputPanel class]]) { panel = nil; } [(TGModernConversationInputTextPanel *)_currentInputPanel setPrimaryExtendedPanel:panel animated:animated skipHeightAnimation:skipHeightAnimation]; } } - (void)setPrimaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)panel animated:(bool)animated { [self setPrimaryExtendedPanel:panel animated:animated skipHeightAnimation:false]; } - (void)setSecondaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)panel animated:(bool)animated skipHeightAnimation:(bool)skipHeightAnimation { [_inputTextPanel setSecondaryExtendedPanel:panel animated:animated]; if (_currentInputPanel != _inputTextPanel && _currentInputPanel != nil && [_currentInputPanel respondsToSelector:@selector(setSecondaryExtendedPanel:animated:skipHeightAnimation:)]) { [(TGModernConversationInputTextPanel *)_currentInputPanel setSecondaryExtendedPanel:panel animated:animated skipHeightAnimation:skipHeightAnimation]; } } - (void)setSecondaryExtendedPanel:(TGModernConversationAssociatedInputPanel *)panel animated:(bool)animated { [self setSecondaryExtendedPanel:panel animated:animated skipHeightAnimation:false]; } - (void)scrollConversationUp { CGFloat newOffset = MIN(_collectionView.contentSize.height + _collectionView.contentInset.bottom - _collectionView.bounds.size.height, _collectionView.contentOffset.y + 75.0f); [_collectionView setContentOffset:CGPointMake(0, newOffset)]; } - (void)scrollConversationDown { CGFloat newOffset = MAX(-_collectionView.contentInset.top, _collectionView.contentOffset.y - 75.0f); [_collectionView setContentOffset:CGPointMake(0, newOffset)]; } - (void)selectPreviousSuggestion { [[_inputTextPanel associatedPanel] selectPreviousItem]; } - (void)selectNextSuggestion { [[_inputTextPanel associatedPanel] selectNextItem]; } - (void)processKeyCommand:(UIKeyCommand *)keyCommand { if ([keyCommand.input isEqualToString:@"\r"]) { if (_inputTextPanel.maybeInputField.isFirstResponder) { if ([_inputTextPanel associatedPanel] != nil) [[_inputTextPanel associatedPanel] commitSelectedItem]; else if ([_inputTextPanel.maybeInputField.text hasNonWhitespaceCharacters]) [self inputPanelRequestedSendMessage:_inputTextPanel text:_inputTextPanel.inputField.text]; } else { [self openKeyboard]; } } else if ([keyCommand.input isEqualToString:@"/"]) { if (keyCommand.modifierFlags & UIKeyModifierCommand) [_inputTextPanel showStickersPanel]; else [_inputTextPanel startCommand]; } else if ([keyCommand.input isEqualToString:@"2"] && keyCommand.modifierFlags & UIKeyModifierShift) { [_inputTextPanel startMention]; } else if ([keyCommand.input isEqualToString:@"3"] && keyCommand.modifierFlags & UIKeyModifierShift) { [_inputTextPanel startHashtag]; } else if ([keyCommand.input isEqualToString:UIKeyInputUpArrow]) { if (keyCommand.modifierFlags & UIKeyModifierShift) [self scrollConversationUp]; else [self selectPreviousSuggestion]; } else if ([keyCommand.input isEqualToString:UIKeyInputDownArrow]) { if (keyCommand.modifierFlags & UIKeyModifierShift) [self scrollConversationDown]; else [self selectNextSuggestion]; } else if ([keyCommand.input isEqualToString:@"I"]) { if (self.associatedPopoverController != nil && [self.associatedPopoverController isKindOfClass:[TGPopoverController class]] && [self.associatedPopoverController.contentViewController isKindOfClass:[TGNavigationController class]] && [((TGNavigationController *)self.associatedPopoverController.contentViewController).viewControllers.firstObject isKindOfClass:[TGCollectionMenuController class]]) { [self.associatedPopoverController dismissPopoverAnimated:true]; if ([self.associatedPopoverController.delegate respondsToSelector:@selector(popoverControllerDidDismissPopover:)]) [self.associatedPopoverController.delegate popoverControllerDidDismissPopover:self.associatedPopoverController]; } else { [self infoButtonPressed]; } } else if ([keyCommand.input isEqualToString:@"W"]) { if (self.associatedPopoverController != nil) { [self.associatedPopoverController dismissPopoverAnimated:true]; if ([self.associatedPopoverController.delegate respondsToSelector:@selector(popoverControllerDidDismissPopover:)]) [self.associatedPopoverController.delegate popoverControllerDidDismissPopover:self.associatedPopoverController]; } else if (_menuController != nil) { [_menuController dismissAnimated:true]; } else { [self closeButtonPressed]; } } } - (NSArray *)availableKeyCommands { NSMutableArray *commands = [[NSMutableArray alloc] init]; if (!_inputTextPanel.maybeInputField.isFirstResponder) { TGKeyCommand *focusKeyCommand = [TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.FocusOnInputField") input:@"\r" modifierFlags:0]; if (![TGAppDelegateInstance.keyCommandController isKeyCommandOccupied:focusKeyCommand]) { [commands addObject:focusKeyCommand]; [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"/" modifierFlags:0]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"2" modifierFlags:UIKeyModifierShift]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"3" modifierFlags:UIKeyModifierShift]]; } } else { [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.SendMessage") input:@"\r" modifierFlags:0]]; if ([_inputTextPanel associatedPanel] != nil) { [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputUpArrow modifierFlags:0]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputDownArrow modifierFlags:0]]; } } [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"/" modifierFlags:UIKeyModifierCommand]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.ScrollUp") input:UIKeyInputUpArrow modifierFlags:UIKeyModifierShift]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.ScrollDown") input:UIKeyInputDownArrow modifierFlags:UIKeyModifierShift]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.ChatInfo") input:@"I" modifierFlags:UIKeyModifierCommand]]; [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"W" modifierFlags:UIKeyModifierCommand]]; return commands; } - (void)messageEditingCancelPressed { [self endMessageEditing:true]; } - (void)messagesDeleted:(NSArray *)messageIds { NSMutableSet *set = [[NSMutableSet alloc] initWithArray:messageIds]; if (_inputTextPanel.messageEditingContext != nil && [set containsObject:@(_inputTextPanel.messageEditingContext.messageId)]) { [self endMessageEditing:true]; } } @end