1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-02 09:27:55 +01:00
Telegram/Telegraph/TGModernConversationCompanion.mm
2016-02-25 01:03:51 +01:00

2845 lines
102 KiB
Plaintext

/*
* This is the source code of Telegram for iOS v. 1.1
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Peter Iakovlev, 2013.
*/
#import "TGModernConversationCompanion.h"
#import "TGModernConversationController.h"
#import "ActionStage.h"
#import "TGDatabase.h"
#import "TGTelegramNetworking.h"
#import "TGAppDelegate.h"
#import "TGInterfaceManager.h"
#import "TGMessage.h"
#import "TGMessageModernConversationItem.h"
#import "TGImageUtils.h"
#import "TGPhoneUtils.h"
#import "TGModernConversationViewContext.h"
#import "TGModernTemporaryView.h"
#import "TGModernViewStorage.h"
#import "TGModernImageView.h"
#import "TGModernImageViewModel.h"
#import "TGModernViewContext.h"
#import "TGModernConversationViewLayout.h"
#import "TGModernDateHeaderView.h"
#import "TGModernUnreadHeaderView.h"
#import "TGModernConversationGenericEmptyListView.h"
#import "TGIndexSet.h"
#import "TGApplication.h"
#import "TGWallpaperManager.h"
#import "TGTelegraph.h"
#import "TGDownloadMessagesSignal.h"
#import "TGUpdateStateRequestBuilder.h"
#import "TGInstagramMediaIdSignal.h"
#import "TGProgressWindow.h"
#import "TGGenericPeerPlaylistSignals.h"
#import "TGGenericModernConversationCompanion.h"
#import "TGChannelManagementSignals.h"
#import <libkern/OSAtomic.h>
#import "TGPeerIdAdapter.h"
#import "TGAlertView.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
#define NEEDS_DISPATCH_RETAIN_RELEASE 0
#else // iOS 5.X or earlier
#define NEEDS_DISPATCH_RETAIN_RELEASE 1
#endif
static OSSpinLock _messagesViewedLock = 0;
static NSMutableDictionary *messagesViewedByPeerId() {
static NSMutableDictionary *dict = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = [[NSMutableDictionary alloc] init];
});
return dict;
}
static void markMessagesAsSeen(int64_t peerId, NSArray *messageIds) {
OSSpinLockLock(&_messagesViewedLock);
NSMutableSet *set = messagesViewedByPeerId()[@(peerId)];
if (set == nil) {
set = [[NSMutableSet alloc] init];
}
[set addObjectsFromArray:messageIds];
OSSpinLockUnlock(&_messagesViewedLock);
}
static SQueue *_messageQueue = nil;
static SQueue *messageQueue()
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
_messageQueue = [[SQueue alloc] init];
});
return _messageQueue;
}
static bool isMessageQueue()
{
return [messageQueue() isCurrentQueue];
}
static void dispatchOnMessageQueue(dispatch_block_t block, bool synchronous)
{
if (synchronous) {
[messageQueue() dispatchSync:block];
} else {
[messageQueue() dispatch:block];
}
}
@interface TGModernConversationCompanion ()
{
bool _hasStarted;
TGModernViewStorage *_tempViewStorage;
void *_tempMemory;
NSIndexSet *_tempVisibleItemsIndices;
CGFloat _controllerWidthForItemCalculation;
dispatch_semaphore_t _sendMessageSemaphore;
int32_t _initialPositionedMessageId;
TGInitialScrollPosition _initialScrollPosition;
TGMessageRange _unreadMessageRange;
std::set<int32_t> _checkedMessages;
std::map<int32_t, int> _messageFlags;
std::map<int32_t, NSTimeInterval> _messageViewDate;
TGMessageModernConversationItem * (*_updateMediaStatusDataImpl)(id, SEL, TGMessageModernConversationItem *);
bool _controllerShowingEmptyState; // Main Thread
NSMutableDictionary *_downloadingMessages;
NSMutableDictionary *_downloadingWebpages;
NSMutableDictionary *_downloadedMessages;
bool _allowMessageDownloads;
bool _askedForSecretPages;
std::set<int32_t> _messageViewsRequested;
NSMutableArray *_messageViewsRequestedBuffer;
STimer *_messageViewsRequestedBufferTimer;
SDisposableSet *_messageViewsDisposable;
SSignalQueue *_mediaUploadQueue;
}
@end
@implementation TGModernConversationCompanion
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_useInitialSnapshot = true;
_actionHandle = [[ASHandle alloc] initWithDelegate:self];
_items = [[NSMutableArray alloc] init];
_tempViewStorage = [[TGModernViewStorage alloc] init];
TGModernConversationViewContext *viewContext = [[TGModernConversationViewContext alloc] init];
viewContext.companion = self;
viewContext.companionHandle = _actionHandle;
viewContext.viewStatusEnabled = true;
viewContext.autoplayAnimations = TGAppDelegateInstance.autoPlayAnimations;
viewContext.playingAudioMessageStatus = [[[TGTelegraphInstance musicPlayer] playingStatus] deliverOn:[SQueue mainQueue]];
__weak TGModernViewContext *weakViewContext = viewContext;
__weak TGModernConversationCompanion *weakSelf = self;
viewContext.playAudioMessageId = ^(int32_t mid)
{
TGModernViewContext *strongViewContext = weakViewContext;
if (strongViewContext != nil) {
TGMessage *message = [TGDatabaseInstance() loadMessageWithMid:mid peerId:strongViewContext.conversation.conversationId];
__strong TGModernConversationCompanion *strongSelf = weakSelf;
if (message == nil && mid >= migratedMessageIdOffset) {
if (strongSelf != nil && ((TGGenericModernConversationCompanion *)strongSelf)->_attachedConversationId != 0) {
message = [TGDatabaseInstance() loadMessageWithMid:mid - migratedMessageIdOffset peerId:((TGGenericModernConversationCompanion *)strongSelf)->_attachedConversationId];
}
}
if (message != nil)
{
bool isVoice = false;
for (id attachment in 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]]) {
isVoice = ((TGDocumentAttributeAudio *)attribute).isVoice;
}
}
break;
}
}
[TGTelegraphInstance.musicPlayer setPlaylist:[TGGenericPeerPlaylistSignals playlistForPeerId:message.cid important:TGMessageSortKeySpace(message.sortKey) == TGMessageSpaceImportant atMessageId:message.mid voice:isVoice] initialItemKey:@(message.mid) metadata:[strongSelf playlistMetadata:isVoice]];
}
}
};
viewContext.pauseAudioMessage = ^
{
[[TGTelegraphInstance musicPlayer] controlPause];
};
viewContext.resumeAudioMessage = ^
{
[[TGTelegraphInstance musicPlayer] controlPlay];
};
_viewContext = viewContext;
_downloadingMessages = [[NSMutableDictionary alloc] init];
_downloadingWebpages = [[NSMutableDictionary alloc] init];
_downloadedMessages = [[NSMutableDictionary alloc] init];
_messageViewsDisposable = [[SDisposableSet alloc] init];
_messageViewsRequestedBufferTimer = [[STimer alloc] initWithTimeout:0.5 repeat:false completion:^{
__strong TGModernConversationCompanion *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf consumeRequestedMessages];
}
} queue:[TGModernConversationCompanion messageQueue]];
_mediaUploadQueue = [[SSignalQueue alloc] init];
}
return self;
}
- (void)dealloc
{
[_actionHandle reset];
[ActionStageInstance() removeWatcher:self];
[_messageViewsDisposable dispose];
#if NEEDS_DISPATCH_RETAIN_RELEASE
if (_sendMessageSemaphore != nil)
dispatch_release(_sendMessageSemaphore);
#endif
_sendMessageSemaphore = nil;
if (_tempMemory != NULL)
{
free(_tempMemory);
_tempMemory = nil;
}
NSDictionary *downloadingMessages = _downloadingMessages;
NSDictionary *downloadingWeblages = _downloadingWebpages;
STimer *messageViewsRequestedBufferTimer = _messageViewsRequestedBufferTimer;
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[messageViewsRequestedBufferTimer invalidate];
[downloadingMessages enumerateKeysAndObjectsUsingBlock:^(__unused id key, id<SDisposable> disposable, __unused BOOL *stop)
{
[disposable dispose];
}];
[downloadingWeblages enumerateKeysAndObjectsUsingBlock:^(__unused id key, id<SDisposable> disposable, __unused BOOL *stop)
{
[disposable dispose];
}];
}];
}
+ (void)warmupResources
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
TGMessage *message = [[TGMessage alloc] init];
message.text = @"abcdefghijklmnopqrstuvwxyz1234567890";
TGMessageModernConversationItem *messageItem = [[TGMessageModernConversationItem alloc] initWithMessage:message context:nil];
[messageItem sizeForContainerSize:CGSizeMake(320.0f, 0.0f)];
[[TGWallpaperManager instance] currentWallpaperImage];
});
}
+ (bool)isMessageQueue
{
return isMessageQueue();
}
+ (SQueue *)messageQueue
{
return messageQueue();
}
+ (void)dispatchOnMessageQueue:(dispatch_block_t)block
{
dispatchOnMessageQueue(block, false);
}
- (void)lockSendMessageSemaphore
{
return;
#if NEEDS_DISPATCH_RETAIN_RELEASE
if (_sendMessageSemaphore != nil)
dispatch_release(_sendMessageSemaphore);
#endif
_sendMessageSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_wait(_sendMessageSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.07 * NSEC_PER_SEC)));
#if NEEDS_DISPATCH_RETAIN_RELEASE
if (_sendMessageSemaphore != nil)
dispatch_release(_sendMessageSemaphore);
#endif
_sendMessageSemaphore = nil;
}
- (void)unlockSendMessageSemaphore
{
if (_sendMessageSemaphore != nil)
dispatch_semaphore_signal(_sendMessageSemaphore);
}
- (void)setInitialMessagePositioning:(int32_t)initialPositionedMessageId position:(TGInitialScrollPosition)position
{
_initialPositionedMessageId = initialPositionedMessageId;
_initialScrollPosition = position;
}
- (int32_t)initialPositioningMessageId
{
return _initialPositionedMessageId;
}
- (TGInitialScrollPosition)initialPositioningScrollPosition
{
return _initialScrollPosition;
}
- (void)setUnreadMessageRange:(TGMessageRange)unreadMessageRange
{
_unreadMessageRange = unreadMessageRange;
}
- (TGMessageRange)unreadMessageRange
{
return _unreadMessageRange;
}
- (CGFloat)initialPositioningOverflowForScrollPosition:(TGInitialScrollPosition)scrollPosition
{
if (scrollPosition == TGInitialScrollPositionTop)
return 28.0f;
return 1.0f;
}
- (void)bindController:(TGModernConversationController *)controller
{
self.controller = controller;
[self loadInitialState];
[ActionStageInstance() dispatchOnStageQueue:^
{
[self subscribeToUpdates];
}];
}
- (void)_ensureReferenceMethod
{
}
- (void)unbindController
{
self.controller = nil;
[ActionStageInstance() dispatchOnStageQueue:^
{
[self _ensureReferenceMethod];
}];
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[self _ensureReferenceMethod];
}];
}
- (void)loadInitialState
{
TGModernConversationController *controller = self.controller;
[controller setConversationHeader:[self _conversationHeader]];
[self _updateInputPanel];
}
- (void)subscribeToUpdates
{
[ActionStageInstance() watchForPath:@"/webpages" watcher:self];
}
- (void)_controllerWillAppearAnimated:(bool)animated firstTime:(bool)firstTime
{
if (firstTime)
{
if (self.useInitialSnapshot && animated && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
[self _createInitialSnapshot];
else
{
for (TGMessageModernConversationItem *item in [_items copy])
{
[self _updateImportantMediaStatusDataInplace:item];
}
TGModernConversationController *controller = _controller;
[controller setInitialSnapshot:NULL backgroundView:nil viewStorage:_tempViewStorage topEdge:0.0f];
}
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[self _updateMediaStatusDataForItemsInIndexSet:_tempVisibleItemsIndices animated:false forceforceCheckDownload:false];
}];
}
}
- (void)_controllerDidAppear:(bool)firstTime
{
TGModernConversationController *controller = _controller;
if (firstTime)
{
#if TARGET_IPHONE_SIMULATOR
//sleep(1);
#endif
[_tempViewStorage allowResurrectionForOperations:^
{
[controller setInitialSnapshot:NULL backgroundView:nil viewStorage:_tempViewStorage topEdge:0.0f];
}];
if (_tempMemory != NULL)
{
free(_tempMemory);
_tempMemory = nil;
}
_tempViewStorage = nil;
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[self _updateMediaStatusDataForCurrentItems];
_allowMessageDownloads = true;
[self _itemsUpdated];
}];
}
}
- (void)_controllerAvatarPressed
{
}
- (void)_dismissController
{
[[TGInterfaceManager instance] dismissConversation];
}
- (void)_setControllerWidthForItemCalculation:(CGFloat)width
{
_controllerWidthForItemCalculation = width;
}
- (void)_loadControllerPrimaryTitlePanel
{
}
- (TGModernConversationEmptyListPlaceholderView *)_conversationEmptyListPlaceholder
{
TGModernConversationGenericEmptyListView *placeholder = [[TGModernConversationGenericEmptyListView alloc] init];
return placeholder;
}
- (TGModernConversationInputPanel *)_conversationGenericInputPanel
{
return nil;
}
- (TGModernConversationInputPanel *)_conversationEmptyListInputPanel
{
return nil;
}
- (void)_updateInputPanel
{
TGModernConversationController *controller = self.controller;
if (_controllerShowingEmptyState)
[controller setCustomInputPanel:[self _conversationEmptyListInputPanel]];
else
[controller setCustomInputPanel:[self _conversationGenericInputPanel]];
}
- (UIView *)_conversationHeader
{
return nil;
}
- (UIView *)_controllerInputTextPanelAccessoryView
{
return nil;
}
- (NSString *)_controllerInfoButtonText
{
return nil;
}
- (void)updateControllerInputText:(NSString *)__unused inputText messageEditingContext:(TGMessageEditingContext *)__unused messageEditingContext
{
}
- (void)controllerDidUpdateTypingActivity
{
}
- (void)controllerDidCancelTypingActivity
{
}
- (void)controllerDidChangeInputText:(NSString *)__unused inputText
{
}
- (void)controllerWantsToSendTextMessage:(NSString *)__unused text entities:(NSArray *)__unused entities asReplyToMessageId:(int32_t)__unused replyMessageId withAttachedMessages:(NSArray *)__unused withAttachedMessages disableLinkPreviews:(bool)__unused disableLinkPreviews botContextResult:(TGBotContextResultAttachment *)__unused botContextResult
{
}
- (void)controllerWantsToSendMapWithLatitude:(double)__unused latitude longitude:(double)__unused longitude venue:(TGVenueAttachment *)__unused venue asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (NSURL *)fileUrlForDocumentMedia:(TGDocumentMediaAttachment *)__unused documentMedia
{
return nil;
}
- (NSDictionary *)imageDescriptionFromImage:(UIImage *)__unused image caption:(NSString *)__unused caption optionalAssetUrl:(NSString *)__unused assetUrl
{
return nil;
}
- (NSDictionary *)imageDescriptionFromBingSearchResult:(TGBingSearchResultItem *)__unused item caption:(NSString *)__unused caption
{
return nil;
}
- (NSDictionary *)imageDescriptionFromExternalImageSearchResult:(TGExternalImageSearchResult *)__unused item text:(NSString *)__unused text botContextResult:(TGBotContextResultAttachment *)__unused botContextResult {
return nil;
}
- (NSDictionary *)documentDescriptionFromGiphySearchResult:(TGGiphySearchResultItem *)__unused item
{
return nil;
}
- (NSDictionary *)documentDescriptionFromExternalGifSearchResult:(TGExternalGifSearchResult *)__unused item text:(NSString *)__unused text botContextResult:(TGBotContextResultAttachment *)__unused botContextResult {
return nil;
}
- (NSDictionary *)imageDescriptionFromMediaAsset:(TGMediaAsset *)__unused asset previewImage:(UIImage *)__unused previewImage document:(bool)__unused document fileName:(NSString *)__unused fileName caption:(NSString *)__unused caption
{
return nil;
}
- (NSDictionary *)videoDescriptionFromMediaAsset:(TGMediaAsset *)__unused asset previewImage:(UIImage *)__unused previewImage adjustments:(TGVideoEditAdjustments *)__unused adjustments document:(bool)__unused document fileName:(NSString *)__unused fileName caption:(NSString *)__unused caption
{
return nil;
}
- (NSDictionary *)documentDescriptionFromICloudDriveItem:(TGICloudItem *)__unused item
{
return nil;
}
- (NSDictionary *)documentDescriptionFromDropboxItem:(TGDropboxItem *)__unused item
{
return nil;
}
- (NSDictionary *)documentDescriptionFromGoogleDriveItem:(TGGoogleDriveItem *)__unused item
{
return nil;
}
- (NSDictionary *)imageDescriptionFromInternalSearchImageResult:(TGWebSearchInternalImageResult *)__unused item caption:(NSString *)__unused caption
{
return nil;
}
- (NSDictionary *)documentDescriptionFromInternalSearchResult:(TGWebSearchInternalGifResult *)__unused item
{
return nil;
}
- (NSDictionary *)documentDescriptionFromRemoteDocument:(TGDocumentMediaAttachment *)__unused document {
return nil;
}
- (NSDictionary *)documentDescriptionFromFileAtTempUrl:(NSURL *)__unused url fileName:(NSString *)__unused fileName mimeType:(NSString *)__unused mimeType isAnimation:(bool)__unused isAnimation
{
return nil;
}
- (void)controllerWantsToSendImagesWithDescriptions:(NSArray *)__unused imageDescriptions asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToSendLocalVideoWithTempFilePath:(NSString *)__unused tempVideoFilePath fileSize:(int32_t)__unused fileSize previewImage:(UIImage *)__unused previewImage duration:(NSTimeInterval)__unused duration dimensions:(CGSize)__unused dimenstions caption:(NSString *)__unused caption assetUrl:(NSString *)__unused assetUrl liveUploadData:(TGLiveUploadActorData *)__unused liveUploadData asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (TGVideoMediaAttachment *)serverCachedAssetWithId:(NSString *)__unused assetId
{
return nil;
}
- (void)controllerWantsToSendDocumentWithTempFileUrl:(NSURL *)__unused tempFileUrl fileName:(NSString *)__unused fileName mimeType:(NSString *)__unused mimeType asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToSendDocumentsWithDescriptions:(NSArray *)__unused descriptions asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToSendRemoteDocument:(TGDocumentMediaAttachment *)__unused document asReplyToMessageId:(int32_t)__unused replyMessageId text:(NSString *)__unused text botContextResult:(TGBotContextResultAttachment *)__unused botContextResult
{
}
- (void)controllerWantsToSendRemoteImage:(TGImageMediaAttachment *)__unused image text:(NSString *)__unused text asReplyToMessageId:(int32_t)__unused replyMessageId botContextResult:(TGBotContextResultAttachment *)__unused botContextResult {
}
- (void)controllerWantsToSendCloudDocumentsWithDescriptions:(NSArray *)__unused descriptions asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToSendLocalAudioWithDataItem:(TGDataItem *)__unused dataItem duration:(NSTimeInterval)__unused duration liveData:(TGLiveUploadActorData *)__unused liveData waveform:(TGAudioWaveform *)__unused waveform asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToSendRemoteVideoWithMedia:(TGVideoMediaAttachment *)__unused media asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToSendContact:(TGUser *)__unused contactUser asReplyToMessageId:(int32_t)__unused replyMessageId
{
}
- (void)controllerWantsToResendMessages:(NSArray *)__unused messageIds
{
}
- (void)controllerWantsToForwardMessages:(NSArray *)__unused messageIds
{
}
- (void)controllerWantsToCreateContact:(int32_t)__unused uid firstName:(NSString *)__unused firstName lastName:(NSString *)__unused lastName phoneNumber:(NSString *)__unused phoneNumber
{
}
- (void)controllerWantsToAddContactToExisting:(int32_t)__unused uid phoneNumber:(NSString *)__unused phoneNumber
{
}
- (void)controllerWantsToApplyLocalization:(NSString *)__unused filePath
{
}
- (void)controllerClearedConversation
{
}
- (void)systemClearedConversation
{
}
- (void)controllerDeletedMessages:(NSArray *)__unused messageIds completion:(void (^)())__unused completion
{
}
- (void)controllerCanReadHistoryUpdated
{
}
- (void)controllerCanRegroupUnreadIncomingMessages
{
}
- (void)controllerRequestedNavigationToConversationWithUser:(int32_t)__unused uid
{
}
- (bool)controllerShouldStoreCapturedAssets
{
return true;
}
- (bool)controllerShouldCacheServerAssets
{
return true;
}
- (bool)controllerShouldLiveUploadVideo
{
return true;
}
- (bool)imageDownloadsShouldAutosavePhotos
{
return false;
}
- (bool)shouldAutomaticallyDownloadPhotos
{
return false;
}
- (bool)shouldAutomaticallyDownloadAnimations
{
return false;
}
- (bool)shouldAutomaticallyDownloadAudios
{
return false;
}
- (bool)allowMessageForwarding
{
return true;
}
- (bool)allowReplies {
return true;
}
- (bool)allowMessageEntities {
return true;
}
- (bool)allowExternalContent {
return true;
}
- (bool)allowContactSharing
{
return true;
}
- (bool)allowVenueSharing
{
return true;
}
- (bool)allowCaptionedMedia
{
return true;
}
- (bool)encryptUploads
{
return false;
}
- (NSDictionary *)userActivityData
{
return nil;
}
- (TGApplicationFeaturePeerType)applicationFeaturePeerType
{
return TGApplicationFeaturePeerPrivate;
}
#pragma mark -
- (void)updateControllerEmptyState
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[self _updateControllerEmptyState:_items.count == 0];
}];
}
- (void)_updateControllerEmptyState:(bool)empty
{
TGDispatchOnMainThread(^
{
if (_controllerShowingEmptyState != empty)
{
_controllerShowingEmptyState = empty;
TGModernConversationController *controller = self.controller;
if (_controllerShowingEmptyState)
[controller setEmptyListPlaceholder:[self _conversationEmptyListPlaceholder]];
else
[controller setEmptyListPlaceholder:nil];
if (_controllerShowingEmptyState)
[controller setCustomInputPanel:[self _conversationEmptyListInputPanel]];
else
[controller setCustomInputPanel:[self _conversationGenericInputPanel]];
}
});
}
- (void)clearCheckedMessages
{
_checkedMessages.clear();
}
- (void)setMessageChecked:(int32_t)messageId checked:(bool)checked
{
if (messageId != 0)
{
if (checked)
_checkedMessages.insert(messageId);
else
_checkedMessages.erase(messageId);
}
}
- (int)checkedMessageCount
{
return (int)_checkedMessages.size();
}
- (NSArray *)checkedMessageIds
{
NSMutableArray *messageIds = [[NSMutableArray alloc] initWithCapacity:_checkedMessages.size()];
for (int32_t mid : _checkedMessages)
{
[messageIds addObject:[[NSNumber alloc] initWithInt:mid]];
}
return messageIds;
}
- (bool)_isMessageChecked:(int32_t)messageId
{
return _checkedMessages.find(messageId) != _checkedMessages.end();
}
- (void)_setMessageFlags:(int32_t)messageId flags:(int)flags
{
TGDispatchOnMainThread(^
{
_messageFlags[messageId] = _messageFlags[messageId] | flags;
TGModernConversationController *controller = _controller;
[controller updateMessageAttributes:messageId];
});
}
- (void)_setMessageViewDate:(int32_t)messageId viewDate:(NSTimeInterval)viewDate
{
TGDispatchOnMainThread(^
{
_messageViewDate[messageId] = viewDate;
TGModernConversationController *controller = _controller;
[controller updateMessageAttributes:messageId];
});
}
- (void)_setMessageFlagsAndViewDate:(int32_t)messageId flags:(int)flags viewDate:(NSTimeInterval)viewDate
{
TGDispatchOnMainThread(^
{
_messageFlags[messageId] = _messageFlags[messageId] | flags;
_messageViewDate[messageId] = viewDate;
TGModernConversationController *controller = _controller;
[controller updateMessageAttributes:messageId];
});
}
- (bool)_isSecretMessageViewed:(int32_t)messageId
{
auto it = _messageFlags.find(messageId);
if (it != _messageFlags.end())
return it->second & TGSecretMessageFlagViewed;
return false;
}
- (bool)_isSecretMessageScreenshotted:(int32_t)messageId
{
auto it = _messageFlags.find(messageId);
if (it != _messageFlags.end())
return it->second & TGSecretMessageFlagScreenshot;
return false;
}
- (NSTimeInterval)_secretMessageViewDate:(int32_t)messageId
{
auto it = _messageViewDate.find(messageId);
if (it != _messageViewDate.end())
return it->second;
return 0.0;
}
#pragma mark -
- (void)_setTitle:(NSString *)title
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setTitle:title];
});
}
- (void)_setAvatarConversationId:(int64_t)conversationId title:(NSString *)title icon:(UIImage *)icon
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setAvatarConversationId:conversationId title:title icon:icon];
});
}
- (void)_setAvatarConversationId:(int64_t)conversationId firstName:(NSString *)firstName lastName:(NSString *)lastName
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setAvatarConversationId:conversationId firstName:firstName lastName:lastName];
});
}
- (void)_setTitleIcons:(NSArray *)titleIcons
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setTitleIcons:titleIcons];
});
}
- (void)_setAvatarUrl:(NSString *)avatarUrl
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setAvatarUrl:avatarUrl];
});
}
- (void)_setStatus:(NSString *)status accentColored:(bool)accentColored allowAnimation:(bool)allowAnimation toggleMode:(TGModernConversationControllerTitleToggle)toggleMode
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setStatus:status accentColored:accentColored allowAnimation:allowAnimation toggleMode:toggleMode];
});
}
- (void)_setTitle:(NSString *)title andStatus:(NSString *)status accentColored:(bool)accentColored allowAnimatioon:(bool)allowAnimation toggleMode:(TGModernConversationControllerTitleToggle)toggleMode
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setTitle:title];
[controller setStatus:status accentColored:accentColored allowAnimation:allowAnimation toggleMode:toggleMode];
});
}
- (void)_setTypingStatus:(NSString *)typingStatus activity:(int)activity
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller setTypingStatus:typingStatus activity:activity];
});
}
#define TG_AUTOMATIC_CONTEXT true
+ (CGContextRef)_createSnapshotContext:(CGSize)screenSize memoryToRelease:(void **)memoryToRelease
{
if (TG_AUTOMATIC_CONTEXT)
{
CGFloat contextScale = TGIsRetina() ? 2.0f : 1.0f;
UIGraphicsBeginImageContextWithOptions(screenSize, false, contextScale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, screenSize.width / 2.0f, screenSize.height / 2.0f);
//CGContextScaleCTM(context, -1.0f, -1.0f);
CGContextRotateCTM(context, (CGFloat)M_PI);
CGContextTranslateCTM(context, -screenSize.width / 2.0f, -screenSize.height / 2.0f);
return context;
}
else
{
CGSize contextSize = screenSize;
if (TGIsRetina())
{
contextSize.width *= 2.0f;
contextSize.height *= 2.0f;
}
size_t bytesPerRow = 4 * (int)contextSize.width;
bytesPerRow = (bytesPerRow + 15) & ~15;
static void *memory = NULL;
if (memoryToRelease != NULL)
{
memory = malloc((int)(bytesPerRow * contextSize.height));
*memoryToRelease = memory;
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
CGContextRef context = CGBitmapContextCreate(memory,
(int)contextSize.width,
(int)contextSize.height,
8,
bytesPerRow,
colorSpace,
bitmapInfo);
CGColorSpaceRelease(colorSpace);
if (TGIsRetina())
CGContextScaleCTM(context, 2.0, 2.0f);
UIGraphicsPushContext(context);
CGContextTranslateCTM(context, screenSize.width / 2.0f, screenSize.height / 2.0f);
CGContextRotateCTM(context, (float)M_PI);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextTranslateCTM(context, -screenSize.width / 2.0f, -screenSize.height / 2.0f);
return context;
}
}
+ (CGImageRef)createSnapshotFromContextAndRelease:(CGContextRef)context
{
if (TG_AUTOMATIC_CONTEXT)
{
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return CGImageRetain(image.CGImage);
}
else
{
UIGraphicsPopContext();
CGImageRef contextImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
return contextImageRef;
}
}
- (void)_createInitialSnapshot
{
#undef TG_TIMESTAMP_DEFINE
#define TG_TIMESTAMP_DEFINE(x)
#undef TG_TIMESTAMP_MEASURE
#define TG_TIMESTAMP_MEASURE(x)
TG_TIMESTAMP_DEFINE(_createInitialSnapshot);
const bool useViews = false;
TGModernConversationController *controller = _controller;
CGSize screenSize = [TGViewController screenSizeForInterfaceOrientation:controller.interfaceOrientation];
if (![self _controllerShouldHideInputTextByDefault] || [controller customInputPanel] != nil) {
screenSize.height -= 45;
}
if (_tempMemory != NULL)
{
free(_tempMemory);
_tempMemory = NULL;
}
CGContextRef context = NULL;
if (!useViews)
{
context = [TGModernConversationCompanion _createSnapshotContext:screenSize memoryToRelease:&_tempMemory];
CGContextSetAllowsFontSubpixelQuantization(context, false);
CGContextSetShouldSubpixelQuantizeFonts(context, false);
CGContextSetAllowsFontSubpixelPositioning(context, true);
CGContextSetShouldSubpixelPositionFonts(context, true);
}
TG_TIMESTAMP_MEASURE(_createInitialSnapshot);
if (context != NULL || useViews)
{
TGModernTemporaryView *backgroundViewContainer = [[TGModernTemporaryView alloc] init];
backgroundViewContainer.viewStorage = _tempViewStorage;
backgroundViewContainer.userInteractionEnabled = false;
NSMutableArray *boundItems = [[NSMutableArray alloc] init];
int scrollItemIndex = 0;
if (_initialPositionedMessageId != 0)
{
int index = -1;
for (TGMessageModernConversationItem *item in [controller _currentItems])
{
index++;
if (item->_message.mid == _initialPositionedMessageId)
{
scrollItemIndex = index;
break;
}
}
}
CGFloat topContentInset = controller.controllerInset.top;
CGFloat contentHeight = 0.0f;
std::vector<TGDecorationViewAttrubutes> visibleDecorationViewAttributes;
NSArray *visibleItemsAttributes = [TGModernConversationViewLayout layoutAttributesForItems:[controller _currentItems] containerWidth:screenSize.width maxHeight:scrollItemIndex == 0 ? screenSize.height : FLT_MAX dateOffset:(int)[[TGTelegramNetworking instance] timeOffset] decorationViewAttributes:&visibleDecorationViewAttributes contentHeight:&contentHeight unreadMessageRange:_unreadMessageRange];
CGFloat contentOffsetY = 0.0f;
if (scrollItemIndex != 0)
{
for (UICollectionViewLayoutAttributes *attributes in visibleItemsAttributes)
{
int index = (int)attributes.indexPath.row;
if (index == scrollItemIndex)
{
switch (_initialScrollPosition)
{
case TGInitialScrollPositionTop:
contentOffsetY = CGRectGetMaxY(attributes.frame) + topContentInset - screenSize.height + [self initialPositioningOverflowForScrollPosition:_initialScrollPosition];
break;
case TGInitialScrollPositionCenter:
{
CGFloat visibleHeight = screenSize.height - topContentInset;
contentOffsetY = CGFloor(CGRectGetMidY(attributes.frame) - visibleHeight / 2.0f);
break;
}
case TGInitialScrollPositionBottom:
contentOffsetY = attributes.frame.origin.y - [self initialPositioningOverflowForScrollPosition:_initialScrollPosition];
break;
default:
break;
}
break;
}
}
if (contentOffsetY > contentHeight + topContentInset - screenSize.height)
contentOffsetY = contentHeight + topContentInset - screenSize.height;
if (contentOffsetY < 0.0f)
contentOffsetY = 0.0f;
}
if (contentOffsetY < 0.0f + FLT_EPSILON && _initialPositionedMessageId != 0)
{
_initialPositionedMessageId = 0;
[self setUnreadMessageRange:TGMessageRangeEmpty()];
visibleDecorationViewAttributes.clear();
visibleItemsAttributes = [TGModernConversationViewLayout layoutAttributesForItems:[controller _currentItems] containerWidth:screenSize.width maxHeight:scrollItemIndex == 0 ? screenSize.height : FLT_MAX dateOffset:(int)[[TGTelegramNetworking instance] timeOffset] decorationViewAttributes:&visibleDecorationViewAttributes contentHeight:&contentHeight unreadMessageRange:_unreadMessageRange];
}
TG_TIMESTAMP_MEASURE(_createInitialSnapshot);
CGFloat topEdge = 0.0f;
NSMutableIndexSet *visibleIndices = [[NSMutableIndexSet alloc] init];
CGRect visibleBounds = CGRectMake(0.0f, contentOffsetY, screenSize.width, screenSize.height);
for (UICollectionViewLayoutAttributes *attributes in visibleItemsAttributes)
{
int index = (int)attributes.indexPath.row;
TGMessageModernConversationItem *item = [controller _currentItems][index];
CGRect itemFrame = attributes.frame;
if (!CGRectIntersectsRect(visibleBounds, itemFrame))
continue;
[self _updateImportantMediaStatusDataInplace:item];
[visibleIndices addIndex:index];
CGFloat currentVerticalPosition = screenSize.height - itemFrame.origin.y - itemFrame.size.height + contentOffsetY;
topEdge = MAX(topEdge, screenSize.height - currentVerticalPosition);
if (useViews)
{
[item.viewModel bindViewToContainer:backgroundViewContainer viewStorage:_tempViewStorage];
[item.viewModel _offsetBoundViews:CGSizeMake(0.0f, currentVerticalPosition)];
}
else
{
CGContextTranslateCTM(context, itemFrame.origin.x, currentVerticalPosition);
[item drawInContext:context];
CGContextTranslateCTM(context, -itemFrame.origin.x, -currentVerticalPosition);
[item bindSpecialViewsToContainer:backgroundViewContainer viewStorage:_tempViewStorage atItemPosition:CGPointMake(0, currentVerticalPosition)];
}
[boundItems addObject:item];
}
for (auto it = visibleDecorationViewAttributes.begin(); it != visibleDecorationViewAttributes.end(); it++)
{
if (!CGRectIntersectsRect(visibleBounds, it->frame))
continue;
CGFloat currentVerticalPosition = screenSize.height - it->frame.origin.y - it->frame.size.height + contentOffsetY;
topEdge = MAX(topEdge, screenSize.height - currentVerticalPosition);
if (it->index == INT_MIN && !useViews)
{
CGContextTranslateCTM(context, 0.0f, currentVerticalPosition);
if (it->index != INT_MIN)
{
[TGModernDateHeaderView drawDate:it->index forContainerWidth:screenSize.width inContext:context andBindBackgroundToContainer:backgroundViewContainer atPosition:CGPointMake(0, currentVerticalPosition)];
}
else
{
[TGModernUnreadHeaderView drawHeaderForContainerWidth:screenSize.width inContext:context andBindBackgroundToContainer:backgroundViewContainer atPosition:CGPointMake(0, currentVerticalPosition)];
}
CGContextTranslateCTM(context, 0.0f, -currentVerticalPosition);
}
else if (it->index != INT_MIN)
{
TGModernDateHeaderView *headerView = [[TGModernDateHeaderView alloc] initWithFrame:CGRectMake(0.0f, currentVerticalPosition, screenSize.width, it->frame.size.height)];
headerView.transform = CGAffineTransformMakeRotation((CGFloat)(M_PI));
[headerView setDate:it->index];
[backgroundViewContainer addSubview:headerView];
}
}
TG_TIMESTAMP_MEASURE(_createInitialSnapshot);
backgroundViewContainer.boundItems = boundItems;
CGImageRef contextImageRef = NULL;
if (useViews)
{
static CGImageRef emptyImage = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
emptyImage = CGImageRetain(TGScaleImageToPixelSize([UIImage imageNamed:@"Transparent.png"], CGSizeMake(2, 2)).CGImage);
});
contextImageRef = CGImageRetain(emptyImage);
}
else
contextImageRef = [TGModernConversationCompanion createSnapshotFromContextAndRelease:context];
[controller setInitialSnapshot:contextImageRef backgroundView:backgroundViewContainer viewStorage:nil topEdge:topEdge + 45.0f];
CGImageRelease(contextImageRef);
TG_TIMESTAMP_MEASURE(_createInitialSnapshot);
_tempVisibleItemsIndices = visibleIndices;
}
else
[controller setInitialSnapshot:NULL backgroundView:nil viewStorage:nil topEdge:0.0f];
}
#pragma mark -
- (void)_updateMessageItemsWithData:(NSArray *)__unused items
{
}
- (void)_updateMediaStatusDataForCurrentItems
{
[self _updateMediaStatusDataForItemsInIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, _items.count)] animated:false forceforceCheckDownload:false];
}
- (void)_updateMediaStatusDataForItemsWithMessageIdsInSet:(NSMutableSet *)messageIds
{
if (messageIds.count == 0)
return;
#ifdef DEBUG
NSAssert([TGModernConversationCompanion isMessageQueue], @"Should be on message queue");
#endif
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
NSInteger index = -1;
for (TGMessageModernConversationItem *item in _items)
{
index++;
if ([messageIds containsObject:@(item->_message.mid)])
[indexSet addIndex:index];
}
if (indexSet.count != 0)
[self _updateMediaStatusDataForItemsInIndexSet:indexSet animated:false forceforceCheckDownload:false];
}
- (void)_updateMediaStatusDataForItemsInIndexSet:(NSIndexSet *)indexSet animated:(bool)animated forceforceCheckDownload:(bool)forceCheckDownload
{
if (indexSet.count == 0)
return;
#ifdef DEBUG
NSAssert([TGModernConversationCompanion isMessageQueue], @"Should be on message queue");
#endif
if (_updateMediaStatusDataImpl == nil)
_updateMediaStatusDataImpl = (TGMessageModernConversationItem * (*)(id, SEL, TGMessageModernConversationItem *))[self methodForSelector:@selector(_updateMediaStatusData:)];
SEL selector = @selector(_updateMediaStatusData:);
NSMutableArray *updatedItems = nil;
NSMutableArray *updatedDelayAvailability = nil;
NSMutableArray *atIndices = nil;
NSMutableArray *highPriorityDownloads = [[NSMutableArray alloc] init];
NSMutableArray *regularDownloads = [[NSMutableArray alloc] init];
NSMutableArray *requestMessages = [[NSMutableArray alloc] init];
bool automaticallyDownloadPhotos = [self shouldAutomaticallyDownloadPhotos];
bool automaticallyDownloadAudios = [self shouldAutomaticallyDownloadAudios];
bool automaticallyDownloadAnimations = [self shouldAutomaticallyDownloadAnimations];
if (_updateMediaStatusDataImpl != NULL)
{
int indexCount = (int)indexSet.count;
NSUInteger indices[indexCount];
[indexSet getIndexes:indices maxCount:indexSet.count inIndexRange:nil];
NSInteger itemCount = (NSInteger)_items.count;
for (int i = 0; i < indexCount; i++)
{
if ((NSInteger)indices[i] > itemCount - 1) {
continue;
}
TGMessageModernConversationItem *previousItem = _items[indices[i]];
TGMessageModernConversationItem *updatedItem = _updateMediaStatusDataImpl(self, selector, previousItem);
TGMessageModernConversationItem *checkItem = updatedItem == nil ? previousItem : updatedItem;
bool downloadMessage = false;
for (TGMediaAttachment *attachment in checkItem->_message.mediaAttachments)
{
switch (attachment.type)
{
case TGImageMediaAttachmentType:
{
if (automaticallyDownloadPhotos) {
downloadMessage = true;
}
CGSize size = CGSizeZero;
NSString *imageUrl = [((TGImageMediaAttachment *)attachment).imageInfo closestImageUrlWithSize:CGSizeMake(1136.0f, 1136.0f) resultingSize:&size pickLargest:true];
if (size.width <= 90.0f + FLT_EPSILON || size.height <= 90.0f + FLT_EPSILON) {
imageUrl = [((TGImageMediaAttachment *)attachment).imageInfo imageUrlForSizeLargerThanSize:CGSizeMake(1136.0f, 1136.0f) actualSize:nil];
}
if ([imageUrl hasPrefix:@"http"]) {
downloadMessage = false;
}
break;
}
case TGAudioMediaAttachmentType:
{
if (automaticallyDownloadAudios) {
downloadMessage = true;
}
break;
}
case TGDocumentMediaAttachmentType:
case TGWebPageMediaAttachmentType:
{
TGDocumentMediaAttachment *document = nil;
TGImageMediaAttachment *image = nil;
if (attachment.type == TGDocumentMediaAttachmentType) {
document = (TGDocumentMediaAttachment *)attachment;
} else if (attachment.type == TGWebPageMediaAttachmentType) {
document = ((TGWebPageMediaAttachment *)attachment).document;
image = ((TGWebPageMediaAttachment *)attachment).photo;
}
if (document != nil) {
bool isVoice = false;
for (id attribute in document.attributes) {
if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) {
isVoice = ((TGDocumentAttributeAudio *)attribute).isVoice;
break;
}
}
if (isVoice) {
downloadMessage = automaticallyDownloadAudios;
} else {
int32_t downloadSize = document.size;
if (automaticallyDownloadAnimations && downloadSize < 1 * 1024 * 1024) {
bool isAnimated = ([[document.mimeType lowercaseString] isEqualToString:@"image/gif"] || [[document.mimeType lowercaseString] isEqualToString:@"video/mp4"]) && ([document isAnimated] || (TGPeerIdIsSecretChat([self requestPeerId]) && checkItem->_message.layer < 45));
bool hasSize = [document.thumbnailInfo imageUrlForLargestSize:NULL] != nil;
for (id attribute in document.attributes) {
if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]]) {
hasSize = true;
break;
} else if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) {
hasSize = true;
break;
}
}
if (isAnimated && hasSize) {
downloadMessage = true;
}
}
}
} else if (image != nil) {
if (automaticallyDownloadPhotos) {
downloadMessage = true;
}
}
break;
}
default:
break;
}
}
if (updatedItem != nil)
{
bool delayUpdateAvailability = false;
if (!updatedItem->_mediaAvailabilityStatus && downloadMessage)
{
delayUpdateAvailability = true;
if (!TGMessageRangeIsEmpty(_unreadMessageRange) && TGMessageRangeContains(_unreadMessageRange, updatedItem->_message.mid, (int)updatedItem->_message.date))
[highPriorityDownloads addObject:updatedItem->_message];
else
[regularDownloads addObject:updatedItem->_message];
}
[(NSMutableArray *)_items replaceObjectAtIndex:indices[i] withObject:updatedItem];
if (updatedItems == nil) {
updatedItems = [[NSMutableArray alloc] init];
}
if (updatedDelayAvailability == nil) {
updatedDelayAvailability = [[NSMutableArray alloc] init];
}
if (atIndices == nil) {
atIndices = [[NSMutableArray alloc] init];
}
[updatedItems addObject:updatedItem];
[updatedDelayAvailability addObject:@(delayUpdateAvailability)];
[atIndices addObject:@(indices[i])];
} else if (forceCheckDownload) {
if (!previousItem->_mediaAvailabilityStatus && downloadMessage)
{
if (!TGMessageRangeIsEmpty(_unreadMessageRange) && TGMessageRangeContains(_unreadMessageRange, previousItem->_message.mid, (int)previousItem->_message.date))
[highPriorityDownloads addObject:previousItem->_message];
else
[regularDownloads addObject:previousItem->_message];
}
}
TGMessageModernConversationItem *messageItem = _items[indices[i]];
if (messageItem->_message.mid < TGMessageLocalMidBaseline)
{
for (TGMediaAttachment *attachment in messageItem->_message.mediaAttachments)
{
if (attachment.type == TGUnsupportedMediaAttachmentType)
{
[requestMessages addObject:[[NSNumber alloc] initWithInt:messageItem->_message.mid]];
break;
}
}
}
}
}
if (updatedItems != nil)
{
[self _itemsUpdated];
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
int index = -1;
for (TGMessageModernConversationItem *messageItem in updatedItems)
{
index++;
[controller updateItemAtIndex:[atIndices[index] unsignedIntegerValue] toItem:messageItem delayAvailability:[updatedDelayAvailability[index] boolValue]];
}
});
}
[self _updateProgressForItemsInIndexSet:indexSet animated:animated];
if (highPriorityDownloads.count != 0 || regularDownloads.count != 0) {
NSMutableArray *downloadList = [[NSMutableArray alloc] init];
for (id message in [highPriorityDownloads reverseObjectEnumerator])
[downloadList addObject:message];
[downloadList addObjectsFromArray:regularDownloads];
for (TGMessage *message in downloadList) {
[self _downloadMediaInMessage:message highPriority:false];
}
}
if (requestMessages.count != 0)
{
static NSMutableDictionary *requestedMessageAccounts = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
requestedMessageAccounts = [[NSMutableDictionary alloc] init];
});
NSMutableSet *messageSet = requestedMessageAccounts[@(TGTelegraphInstance.clientUserId)];
if (messageSet == nil)
{
messageSet = [[NSMutableSet alloc] init];
requestedMessageAccounts[@(TGTelegraphInstance.clientUserId)] = messageSet;
}
NSMutableArray *requestMids = [[NSMutableArray alloc] init];
for (NSNumber *nMid in requestMessages)
{
if (![messageSet containsObject:nMid])
{
[messageSet addObject:nMid];
[requestMids addObject:nMid];
}
}
if (requestMids.count != 0)
{
[ActionStageInstance() dispatchOnStageQueue:^
{
for (NSNumber *nMid in requestMids)
{
NSString *action = [[NSString alloc] initWithFormat:@"/tg/downloadMessages/(%d)", [nMid intValue]];
NSDictionary *options = @{@"mids": @[nMid]};
[ActionStageInstance() requestActor:action options:options flags:0 watcher:self];
[ActionStageInstance() requestActor:action options:options flags:0 watcher:TGTelegraphInstance];
}
}];
}
}
}
- (void)_updateProgressForItemsInIndexSet:(NSIndexSet *)__unused indexSet animated:(bool)__unused animated
{
}
- (TGMessageModernConversationItem *)_updateMediaStatusData:(TGMessageModernConversationItem *)__unused item
{
return nil;
}
- (void)_updateImportantMediaStatusDataInplace:(TGMessageModernConversationItem *)__unused item
{
}
#pragma mark -
- (void)loadMoreMessagesAbove
{
}
- (void)loadMoreMessagesBelow
{
}
- (void)unloadMessagesAbove
{
}
- (void)unloadMessagesBelow
{
}
#pragma mark -
- (void)_performFastScrollDown:(bool)__unused becauseOfSendTextAction
{
}
- (void)_replaceMessages:(NSArray *)newMessages
{
[self _replaceMessages:newMessages atMessageId:0 expandFrom:0 jump:false top:false messageIdForVisibleHoleDirection:0 scrollBackMessageId:0 animated:false];
}
- (void)_replaceMessages:(NSArray *)newMessages atMessageId:(int32_t)atMessageId expandFrom:(int32_t)expandMessageId jump:(bool)jump top:(bool)top messageIdForVisibleHoleDirection:(int32_t)messageIdForVisibleHoleDirection scrollBackMessageId:(int32_t)scrollBackMessageId animated:(bool)animated
{
[(NSMutableArray *)_items removeAllObjects];
for (TGMessage *message in newMessages)
{
TGMessageModernConversationItem *messageItem = [[TGMessageModernConversationItem alloc] initWithMessage:message context:_viewContext];
[(NSMutableArray *)_items addObject:messageItem];
}
[self _updateMessageItemsWithData:_items];
[self _itemsUpdated];
NSArray *itemsCopy = [[NSArray alloc] initWithArray:_items];
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
if (atMessageId != 0) {
[controller replaceItems:itemsCopy positionAtMessageId:atMessageId expandAt:expandMessageId jump:jump top:top messageIdForVisibleHoleDirection:messageIdForVisibleHoleDirection scrollBackMessageId:scrollBackMessageId animated:animated];
} else {
[controller replaceItems:itemsCopy messageIdForVisibleHoleDirection:messageIdForVisibleHoleDirection];
}
});
[self _updateControllerEmptyState:_items.count == 0];
[TGModernConversationCompanion dispatchOnMessageQueue:^{
[self _updateMediaStatusDataForCurrentItems];
}];
}
- (void)_replaceMessagesWithFastScroll:(NSArray *)newMessages intent:(TGModernConversationAddMessageIntent)intent scrollToMessageId:(int32_t)scrollToMessageId scrollBackMessageId:(int32_t)scrollBackMessageId animated:(bool)animated
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[(NSMutableArray *)_items removeAllObjects];
for (TGMessage *message in newMessages)
{
TGMessageModernConversationItem *messageItem = [[TGMessageModernConversationItem alloc] initWithMessage:message context:_viewContext];
[(NSMutableArray *)_items addObject:messageItem];
}
[self _updateMessageItemsWithData:_items];
NSArray *itemsCopy = [[NSArray alloc] initWithArray:_items];
TGDispatchOnMainThread(^
{
TGModernConversationInsertItemIntent insertIntent = TGModernConversationInsertItemIntentGeneric;
switch (intent)
{
case TGModernConversationAddMessageIntentSendTextMessage:
insertIntent = TGModernConversationInsertItemIntentSendTextMessage;
break;
case TGModernConversationAddMessageIntentSendOtherMessage:
insertIntent = TGModernConversationInsertItemIntentSendOtherMessage;
break;
default:
break;
}
TGModernConversationController *controller = _controller;
if (insertIntent == TGModernConversationInsertItemIntentSendTextMessage)
[controller setEnableSendButton:true];
[controller replaceItemsWithFastScroll:itemsCopy intent:insertIntent scrollToMessageId:scrollToMessageId scrollBackMessageId:scrollBackMessageId animated:animated];
[controller setEnableBelowHistoryRequests:false];
[controller setEnableAboveHistoryRequests:true];
});
[self _updateMediaStatusDataForCurrentItems];
[self _updateControllerEmptyState:_items.count == 0];
[self _itemsUpdated];
}];
}
- (void)_addMessages:(NSArray *)addedMessages animated:(bool)animated intent:(TGModernConversationAddMessageIntent)intent
{
[self _addMessages:addedMessages animated:animated intent:intent deletedMessageIds:nil];
}
- (void)_addMessages:(NSArray *)addedMessages animated:(bool)animated intent:(TGModernConversationAddMessageIntent)intent deletedMessageIds:(NSArray *)deletedMessageIds
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
std::set<int32_t> removedMids;
for (NSNumber *nMid in deletedMessageIds)
{
removedMids.insert([nMid intValue]);
}
NSMutableIndexSet *deletedIndexSet = [[NSMutableIndexSet alloc] init];
int index = -1;
for (TGMessageModernConversationItem *item in _items)
{
index++;
if (removedMids.find(item->_message.mid) != removedMids.end())
{
[deletedIndexSet addIndex:index];
}
}
[(NSMutableArray *)_items removeObjectsAtIndexes:deletedIndexSet];
std::set<int32_t> existingMids;
for (TGMessageModernConversationItem *messageItem in _items)
{
int32_t mid = messageItem->_message.mid;
existingMids.insert(mid);
}
TGMutableArrayWithIndices *insertArray = [[TGMutableArrayWithIndices alloc] initWithArray:(NSMutableArray *)_items];
for (TGMessage *message in addedMessages)
{
if (existingMids.find(message.mid) != existingMids.end())
continue;
existingMids.insert(message.mid);
int date = (int)message.date;
int32_t mid = message.mid;
bool inserted = false;
int index = -1;
for (TGMessageModernConversationItem *messageItem in _items)
{
index++;
int itemDate = (int)messageItem->_message.date;
int32_t itemMid = messageItem->_message.mid;
bool passes = false;
if (itemMid < 0 && mid < 0) {
passes = itemMid > mid;
} else {
passes = itemMid < mid;
}
if (itemDate < date || (itemDate == date && passes))
{
[insertArray insertObject:[[TGMessageModernConversationItem alloc] initWithMessage:message context:_viewContext] atIndex:index];
inserted = true;
break;
}
}
if (!inserted) {
[insertArray insertObject:[[TGMessageModernConversationItem alloc] initWithMessage:message context:_viewContext] atIndex:_items.count];
}
}
NSIndexSet *insertAtIndices = nil;
NSArray *insertItems = [insertArray objectsForInsertOperations:&insertAtIndices];
[self _updateMessageItemsWithData:insertItems];
for (TGModernConversationItem *item in insertItems)
{
[item sizeForContainerSize:CGSizeMake(_controllerWidthForItemCalculation, 0.0f)];
}
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
TGModernConversationInsertItemIntent insertIntent = TGModernConversationInsertItemIntentGeneric;
switch (intent)
{
case TGModernConversationAddMessageIntentLoadMoreMessagesAbove:
insertIntent = TGModernConversationInsertItemIntentLoadMoreMessagesAbove;
break;
case TGModernConversationAddMessageIntentLoadMoreMessagesBelow:
insertIntent = TGModernConversationInsertItemIntentLoadMoreMessagesBelow;
break;
case TGModernConversationAddMessageIntentSendTextMessage:
insertIntent = TGModernConversationInsertItemIntentSendTextMessage;
break;
case TGModernConversationAddMessageIntentSendOtherMessage:
insertIntent = TGModernConversationInsertItemIntentSendOtherMessage;
break;
default:
break;
}
[controller insertItems:insertItems atIndices:insertAtIndices animated:animated intent:insertIntent removeAtIndices:deletedIndexSet];
if (intent == TGModernConversationAddMessageIntentSendTextMessage)
[controller setEnableSendButton:true];
});
[self _updateMediaStatusDataForItemsInIndexSet:insertAtIndices animated:false forceforceCheckDownload:false];
[self _updateControllerEmptyState:_items.count == 0];
[self _itemsUpdated];
}];
}
- (void)_deleteMessages:(NSArray *)messageIds animated:(bool)animated
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
std::set<int32_t> removedMids;
for (NSNumber *nMid in messageIds)
{
removedMids.insert([nMid intValue]);
}
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
int index = -1;
for (TGMessageModernConversationItem *item in _items)
{
index++;
if (removedMids.find(item->_message.mid) != removedMids.end())
{
[indexSet addIndex:index];
}
}
[(NSMutableArray *)_items removeObjectsAtIndexes:indexSet];
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller _deleteItemsAtIndices:indexSet animated:animated animationFactor:1.0f];
if (!_checkedMessages.empty())
{
bool haveChanges = false;
for (NSNumber *nMid in messageIds)
{
if (_checkedMessages.find([nMid intValue]) != _checkedMessages.end())
{
_checkedMessages.erase([nMid intValue]);
haveChanges = true;
}
}
if (haveChanges)
[controller updateCheckedMessages];
}
[controller messagesDeleted:messageIds];
});
[self _updateControllerEmptyState:_items.count == 0];
[self _itemsUpdated];
}];
}
- (void)_updateMessagesRead:(NSArray *)messageIds
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
std::set<int32_t> readMids;
for (NSNumber *nMid in messageIds)
{
readMids.insert([nMid intValue]);
}
NSMutableArray *itemUpdates = [[NSMutableArray alloc] init];
int count = (int)_items.count;
for (int index = 0; index < count; index++)
{
TGMessageModernConversationItem *messageItem = _items[index];
if (readMids.find(messageItem->_message.mid) != readMids.end())
{
TGMessageModernConversationItem *updatedItem = [messageItem deepCopy];
updatedItem->_message.unread = false;
[(NSMutableArray *)_items replaceObjectAtIndex:index withObject:updatedItem];
[itemUpdates addObject:[[NSNumber alloc] initWithInt:index]];
[itemUpdates addObject:updatedItem];
}
}
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
int updatedItemsCount = (int)itemUpdates.count;
for (int i = 0; i < updatedItemsCount; i += 2)
{
[controller updateItemAtIndex:[itemUpdates[i + 0] intValue] toItem:itemUpdates[i + 1] delayAvailability:false];
}
});
}];
}
- (void)_updateMessageDelivered:(int32_t)previousMid
{
[self _updateMessageDelivered:previousMid mid:0 date:0 message:nil unread:nil pts:0];
}
- (void)_updateMessageDelivered:(int32_t)previousMid mid:(int32_t)mid date:(int32_t)date message:(TGMessage *)message unread:(NSNumber *)unread pts:(int32_t)pts
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
int index = -1;
int foundIndex = -1;
for (TGMessageModernConversationItem *messageItem in _items)
{
index++;
if (messageItem->_message.mid == previousMid)
{
TGMessageModernConversationItem *updatedItem = [messageItem deepCopy];
TGMessage *updatedMessage = updatedItem->_message;
if (message != nil)
updatedMessage.mediaAttachments = message.mediaAttachments;
if (mid != 0)
updatedMessage.mid = mid;
if (date != 0)
updatedItem->_additionalDate = date;
if (unread != nil)
updatedMessage.unread = [unread boolValue];
updatedMessage.pts = pts;
updatedMessage.deliveryState = TGMessageDeliveryStateDelivered;
updatedItem->_message = updatedMessage;
TGMessageModernConversationItem *statusItem = [self _updateMediaStatusData:updatedItem];
bool updateMediaAvailability = false;
if (statusItem != nil)
{
updatedItem = statusItem;
updateMediaAvailability = true;
}
if (messageItem->_message.deliveryState != TGMessageDeliveryStateDelivered && TGAppDelegateInstance.soundEnabled)
[TGAppDelegateInstance playSound:@"sent.caf" vibrate:false];
[(NSMutableArray *)_items replaceObjectAtIndex:index withObject:updatedItem];
foundIndex = index;
TGDispatchOnMainThread(^
{
if (mid != 0 && _mediaHiddenMessageId == previousMid)
_mediaHiddenMessageId = mid;
TGModernConversationController *controller = _controller;
[controller updateItemAtIndex:index toItem:updatedItem delayAvailability:false];
});
break;
}
}
[self _itemsUpdated];
if (foundIndex >= 0) {
[self _updateMediaStatusDataForItemsInIndexSet:[NSIndexSet indexSetWithIndex:foundIndex] animated:false forceforceCheckDownload:true];
}
TGDispatchOnMainThread(^
{
if (mid != 0 && _checkedMessages.find(previousMid) != _checkedMessages.end())
{
_checkedMessages.erase(previousMid);
_checkedMessages.insert(mid);
}
});
}];
}
- (void)_updateMessageDeliveryFailed:(int32_t)previousMid
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
int index = -1;
for (TGMessageModernConversationItem *messageItem in _items)
{
index++;
if (messageItem->_message.mid == previousMid)
{
TGMessageModernConversationItem *updatedItem = [messageItem deepCopy];
updatedItem->_message.deliveryState = TGMessageDeliveryStateFailed;
[(NSMutableArray *)_items replaceObjectAtIndex:index withObject:updatedItem];
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
[controller updateItemAtIndex:index toItem:updatedItem delayAvailability:false];
});
break;
}
}
}];
}
- (void)_downloadMediaInMessage:(TGMessage *)__unused message highPriority:(bool)__unused highPriority
{
}
- (void)_updateMessages:(NSDictionary *)messagesByIds
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
NSMutableArray *itemUpdates = [[NSMutableArray alloc] init];
NSUInteger count = _items.count;
for (NSUInteger index = 0; index < count; index++)
{
TGMessageModernConversationItem *messageItem = _items[index];
TGMessage *previousMessage = messageItem->_message;
TGMessage *updatedMessage = [messagesByIds[@(previousMessage.mid)] copy];
if (updatedMessage != nil && ![updatedMessage isEqual:previousMessage])
{
updatedMessage.date = previousMessage.date;
TGMessageModernConversationItem *updatedItem = [messageItem deepCopy];
updatedItem->_message = updatedMessage;
[(NSMutableArray *)_items replaceObjectAtIndex:index withObject:updatedItem];
[itemUpdates addObject:[[NSNumber alloc] initWithInt:(int)index]];
[itemUpdates addObject:updatedItem];
}
}
if (itemUpdates.count != 0)
{
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
int updatedItemsCount = (int)itemUpdates.count;
for (int i = 0; i < updatedItemsCount; i += 2)
{
[controller updateItemAtIndex:[itemUpdates[i + 0] intValue] toItem:itemUpdates[i + 1] delayAvailability:false];
}
});
[self _itemsUpdated];
}
}];
}
#pragma mark -
- (NSString *)instagramShortcodeFromText:(NSString *)text
{
NSArray *prefixList = @[
@"http://instagram.com/p/",
@"https://instagram.com/p/",
@"http://www.instagram.com/p/",
@"https://www.instagram.com/p/",
@"instagram.com/p/",
@"www.instagram.com/p/",
];
NSString *instagramPrefix = nil;
for (NSString *prefix in prefixList) {
if ([text hasPrefix:prefix]) {
instagramPrefix = prefix;
break;
}
}
if (instagramPrefix.length != 0)
{
NSString *prefix = instagramPrefix;
int length = (int)text.length;
bool badCharacters = false;
int slashCount = 0;
for (int i = (int)prefix.length; i < length; i++)
{
unichar c = [text characterAtIndex:i];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '/' || c == '-')
{
if (c == '/')
{
if (slashCount >= 2)
{
badCharacters = true;
break;
}
slashCount++;
}
}
else
{
badCharacters = true;
break;
}
}
if (!badCharacters)
{
NSString *shortcode = [text substringFromIndex:prefix.length];
if ([shortcode hasSuffix:@"/"])
shortcode = [shortcode substringToIndex:shortcode.length - 1];
return shortcode;
}
}
return nil;
}
- (NSString *)youtubeVideoIdFromText:(NSString *)text
{
if ([text hasPrefix:@"http://www.youtube.com/watch?v="] || [text hasPrefix:@"https://www.youtube.com/watch?v="] || [text hasPrefix:@"http://m.youtube.com/watch?v="] || [text hasPrefix:@"https://m.youtube.com/watch?v="])
{
NSRange range1 = [text rangeOfString:@"?v="];
bool match = true;
for (NSInteger i = range1.location + range1.length; i < (NSInteger)text.length; i++)
{
unichar c = [text characterAtIndex:i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#'))
{
match = false;
break;
}
}
if (match)
{
NSString *videoId = nil;
NSRange ampRange = [text rangeOfString:@"&"];
NSRange hashRange = [text rangeOfString:@"#"];
if (ampRange.location != NSNotFound || hashRange.location != NSNotFound)
{
NSInteger location = MIN(ampRange.location, hashRange.location);
videoId = [text substringWithRange:NSMakeRange(range1.location + range1.length, location - range1.location - range1.length)];
}
else
videoId = [text substringFromIndex:range1.location + range1.length];
if (videoId.length != 0)
return videoId;
}
}
else if ([text hasPrefix:@"http://youtu.be/"] || [text hasPrefix:@"https://youtu.be/"])
{
NSString *suffix = [text substringFromIndex:[text hasPrefix:@"http://youtu.be/"] ? @"http://youtu.be/".length : @"https://youtu.be/".length];
for (int i = 0; i < (int)suffix.length; i++)
{
unichar c = [suffix characterAtIndex:i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#'))
{
return nil;
}
}
}
return nil;
}
- (NSString *)twitterPostIdFromText:(NSString *)text
{
bool isHttps = false;
if ([text hasPrefix:@"http://twitter.com/"] || (isHttps = [text hasPrefix:@"https://twitter.com/"]))
{
NSString *path = [text substringFromIndex:(isHttps ? @"https://twitter.com/" : @"http://twitter.com/").length];
NSArray *components = [path componentsSeparatedByString:@"/"];
if (components.count == 3 && [(NSString *)components[1] isEqualToString:@"status"])
return (NSString *)components[2];
}
return nil;
}
- (NSString *)vkInternalUrlFromText:(NSString *)text
{
NSString *pattern = @"https?:\\/\\/(vk\\.com\\/wall-?[0-9_-]+)\\/?";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
NSTextCheckingResult *match = [regex firstMatchInString:text options:0 range:NSMakeRange(0, [text length])];
if (match != nil)
return [text substringWithRange:[match rangeAtIndex:1]];
return nil;
}
- (void)actionStageActionRequested:(NSString *)action options:(id)options
{
if ([action isEqualToString:@"messageSelectionRequested"])
{
TGModernConversationController *controller = _controller;
[controller highlightAndShowActionsMenuForMessage:[options[@"mid"] int32Value]];
}
else if ([action isEqualToString:@"messageSelectionChanged"])
{
int32_t mid = [options[@"mid"] int32Value];
if (mid != 0)
{
if ([options[@"selected"] boolValue])
_checkedMessages.insert(mid);
else
_checkedMessages.erase(mid);
TGModernConversationController *controller = _controller;
[controller updateCheckedMessages];
}
}
else if ([action isEqualToString:@"openLinkWithOptionsRequested"])
{
TGModernConversationController *controller = _controller;
[controller showActionsMenuForLink:options[@"url"] webPage:options[@"webPage"]];
}
else if ([action isEqualToString:@"openLinkRequested"])
{
if ([options[@"url"] hasPrefix:@"tel:"])
{
NSString *rawPhone = [options[@"url"] substringFromIndex:4];
rawPhone = [TGPhoneUtils cleanInternationalPhone:rawPhone forceInternational:false];
[TGAppDelegateInstance performPhoneCall:[NSURL URLWithString:[@"tel:" stringByAppendingString:rawPhone]]];
}
else
{
NSString *youtubeVideoId = [self youtubeVideoIdFromText:options[@"url"]];
if (youtubeVideoId.length != 0)
{
NSURL *clientUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"youtube-x-callback://watch?v=%@&x-success=telegram://1&x-source=Telegram", youtubeVideoId]];
if ([[UIApplication sharedApplication] canOpenURL:clientUrl])
{
[[UIApplication sharedApplication] openURL:clientUrl];
return;
}
}
NSString *twitterPostId = [self twitterPostIdFromText:options[@"url"]];
if (twitterPostId.length != 0)
{
NSURL *clientUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"twitter://status?id=%@", twitterPostId]];
if ([[UIApplication sharedApplication] canOpenURL:clientUrl])
{
[[UIApplication sharedApplication] openURL:clientUrl];
return;
}
}
NSString *instagramShortcode = [self instagramShortcodeFromText:options[@"url"]];
if (instagramShortcode.length != 0)
{
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"instagram://media?id=1"]])
{
TGProgressWindow *progressWindow = [[TGProgressWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[progressWindow show:true];
[[[[TGInstagramMediaIdSignal instagramMediaIdForShortcode:instagramShortcode] deliverOn:[SQueue mainQueue]] onDispose:^
{
[progressWindow dismiss:true];
}] startWithNext:^(NSString *mediaId)
{
NSURL *clientUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"instagram://media?id=%@", mediaId]];
if ([[UIApplication sharedApplication] canOpenURL:clientUrl])
{
[[UIApplication sharedApplication] openURL:clientUrl];
return;
}
} error:^(__unused id error)
{
[(TGApplication *)[UIApplication sharedApplication] openURL:[NSURL URLWithString:options[@"url"]] forceNative:true];
} completed:nil];
}
else
{
[(TGApplication *)[UIApplication sharedApplication] openURL:[NSURL URLWithString:options[@"url"]] forceNative:true];
}
return;
}
NSString *vkUrl = [self vkInternalUrlFromText:options[@"url"]];
if (vkUrl.length != 0)
{
NSURL *clientUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"vk://%@", vkUrl]];
if ([[UIApplication sharedApplication] canOpenURL:clientUrl])
{
[[UIApplication sharedApplication] openURL:clientUrl];
return;
}
}
[(TGApplication *)[UIApplication sharedApplication] openURL:[NSURL URLWithString:options[@"url"]] forceNative:true];
}
}
else if ([action isEqualToString:@"openMediaRequested"])
{
TGModernConversationController *controller = _controller;
[controller openMediaFromMessage:[options[@"mid"] intValue] instant:[options[@"instant"] boolValue]];
}
else if ([action isEqualToString:@"openEmbedRequested"])
{
TGModernConversationController *controller = _controller;
[controller openEmbed:options[@"webPage"]];
}
else if ([action isEqualToString:@"closeMediaRequested"])
{
TGModernConversationController *controller = _controller;
[controller closeMediaFromMessage:[options[@"mid"] intValue] instant:[options[@"instant"] boolValue]];
}
else if ([action isEqualToString:@"showUnsentMessageMenu"])
{
TGModernConversationController *controller = _controller;
[controller showActionsMenuForUnsentMessage:[options[@"mid"] intValue]];
}
}
- (void)actionStageResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)__unused arguments
{
if ([path isEqualToString:@"/webpages"])
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
[self _webPagesUpdated:resource localIdToWebPage:nil];
}];
}
}
- (void)actorMessageReceived:(NSString *)__unused path messageType:(NSString *)__unused messageType message:(id)__unused message
{
}
- (void)actorCompleted:(int)__unused status path:(NSString *)__unused path result:(id)__unused result
{
if ([path hasPrefix:@"/tg/downloadMessages/"])
{
if (status == ASStatusSuccess)
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
NSMutableArray *replacedItems = [[NSMutableArray alloc] init];
[result[@"messagesByConversation"] enumerateKeysAndObjectsUsingBlock:^(__unused id key, NSArray *updatedMessages, __unused BOOL *stop)
{
std::map<int, TGMessage *> updatedMessagesWithMids;
for (TGMessage *message in updatedMessages)
{
updatedMessagesWithMids[message.mid] = message;
}
int index = -1;
for (TGMessageModernConversationItem *messageItem in _items)
{
index++;
auto it = updatedMessagesWithMids.find(messageItem->_message.mid);
if (it != updatedMessagesWithMids.end())
{
TGMessageModernConversationItem *updatedItem = [[TGMessageModernConversationItem alloc] initWithMessage:it->second context:_viewContext];
[indexSet addIndex:index];
[replacedItems addObject:updatedItem];
}
}
}];
[(NSMutableArray *)_items replaceObjectsAtIndexes:indexSet withObjects:replacedItems];
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = self.controller;
[controller replaceItems:replacedItems atIndices:indexSet];
});
[self _updateMediaStatusDataForItemsInIndexSet:indexSet animated:false forceforceCheckDownload:false];
[self _itemsUpdated];
}];
}
}
}
- (void)updateMediaAccessTimeForMessageId:(int32_t)__unused messageId
{
}
- (id)acquireAudioRecordingActivityHolder
{
return nil;
}
- (id)acquireLocationPickingActivityHolder
{
return nil;
}
- (void)serviceNotificationsForMessageIds:(NSArray *)__unused messageIds
{
}
- (void)markMessagesAsViewed:(NSArray *)__unused messageIds
{
}
- (SSignal *)userListForMention:(NSString *)__unused mention canBeContextBot:(bool)__unused canBeContextBot
{
return nil;
}
- (SSignal *)inlineResultForMentionText:(NSString *)__unused mention text:(NSString *)__unused text {
return nil;
}
- (SSignal *)hashtagListForHashtag:(NSString *)__unused hashtag
{
return nil;
}
- (SSignal *)commandListForCommand:(NSString *)__unused command
{
return nil;
}
- (void)navigateToMessageId:(int32_t)__unused messageId scrollBackMessageId:(int32_t)__unused scrollBackMessageId animated:(bool)__unused animated
{
}
- (void)_itemsUpdated
{
if (_allowMessageDownloads)
{
__weak TGModernConversationCompanion *weakSelf = self;
NSTimeInterval remoteTime = [[TGTelegramNetworking instance] globalTime];
NSMutableSet *webPageIds = nil;
NSUInteger itemsCount = _items.count;
for (NSUInteger itemIndex = 0; itemIndex < itemsCount; itemIndex++)
{
TGMessageModernConversationItem *item = _items[itemIndex];
int32_t messageId = item->_message.mid;
int64_t messageCid = item->_message.cid;
if (item->_message.mediaAttachments != nil)
{
for (TGMediaAttachment *attachment in item->_message.mediaAttachments)
{
if (attachment.type == TGWebPageMediaAttachmentType)
{
TGWebPageMediaAttachment *webPage = (TGWebPageMediaAttachment *)attachment;
if (webPage.webPageLocalId != 0 && webPage.url.length != 0) {
TGWebPageMediaAttachment *cachedWebPage = [TGUpdateStateRequestBuilder webPageWithLink:webPage.url];
if (_downloadingWebpages[webPage.url] == nil)
{
if (cachedWebPage != nil) {
[self _webPagesUpdated:@[cachedWebPage] localIdToWebPage:@{@(webPage.webPageLocalId) : cachedWebPage}];
} else {
_downloadingWebpages[webPage.url] = [[TGUpdateStateRequestBuilder requestWebPageByText:webPage.url] startWithNext:^(TGWebPageMediaAttachment *updatedWebPage)
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
__strong TGModernConversationCompanion *strongSelf = weakSelf;
if (strongSelf != nil && updatedWebPage != nil)
{
[strongSelf->_downloadingWebpages removeObjectForKey:webPage.url];
[strongSelf _webPagesUpdated:@[updatedWebPage] localIdToWebPage:@{@(webPage.webPageLocalId): updatedWebPage}];
}
}];
} error:^(__unused id error)
{
[TGDatabaseInstance() updateMessage:messageId peerId:messageCid flags:std::vector<TGDatabaseMessageFlagValue>() media:@[] dispatch:false];
/*[TGModernConversationCompanion dispatchOnMessageQueue:^
{
__strong TGModernConversationCompanion *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_downloadingMessages removeObjectForKey:nKey];
strongSelf->_downloadedMessages[nKey] = @1;
}
}];*/
} completed:nil];
}
}
} else if (webPage.webPageId != 0) {
NSNumber *nKey = @(webPage.webPageId);
[webPageIds addObject:nKey];
if (webPage.pendingDate != 0 && webPage.url.length == 0)
{
NSTimeInterval delay = MAX(1.0, webPage.pendingDate - remoteTime);
if (_downloadedMessages[nKey] == nil && _downloadingMessages[nKey] == nil)
{
TGWebPageMediaAttachment *cachedWebPage = [TGUpdateStateRequestBuilder webPageWithId:webPage.webPageId];
if (cachedWebPage != nil)
[self _webPagesUpdated:@[cachedWebPage] localIdToWebPage:nil];
else
{
TGGenericModernConversationCompanion *genericSelf = (TGGenericModernConversationCompanion *)self;
_downloadingMessages[nKey] = [[[TGDownloadMessagesSignal downloadMessages:@[[[TGDownloadMessage alloc] initWithPeerId:genericSelf->_conversationId accessHash:genericSelf->_accessHash messageId:item->_message.mid]]] delay:delay onQueue:[SQueue concurrentDefaultQueue]] startWithNext:^(NSArray *messages)
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
__strong TGModernConversationCompanion *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_downloadingMessages removeObjectForKey:nKey];
[strongSelf _messagesDownloaded:messages];
}
}];
} error:^(__unused id error)
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
__strong TGModernConversationCompanion *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_downloadingMessages removeObjectForKey:nKey];
strongSelf->_downloadedMessages[nKey] = @1;
}
}];
} completed:nil];
}
}
}
}
break;
}
}
}
}
for (NSNumber *nPageId in webPageIds)
{
id<SDisposable> disposable = _downloadingMessages[nPageId];
if (disposable != nil)
{
[disposable dispose];
[_downloadingMessages removeObjectForKey:nPageId];
}
}
}
}
- (void)updateMessageViews:(NSDictionary *)messageIdToViews markAsSeen:(bool)markAsSeen {
[TGModernConversationCompanion dispatchOnMessageQueue:^{
__strong TGModernConversationCompanion *strongSelf = self;
if (strongSelf != nil) {
NSMutableArray *updatedItems = [[NSMutableArray alloc] init];
NSMutableArray *updatedItemIndices = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < strongSelf->_items.count; i++) {
TGMessageModernConversationItem *item = strongSelf->_items[i];
NSNumber *nViewCount = messageIdToViews[@(item->_message.mid)];
if (nViewCount != nil) {
TGMessageModernConversationItem *updatedItem = [item deepCopy];
updatedItem->_message.viewCount = [[TGMessageViewCountContentProperty alloc] initWithViewCount:MAX(updatedItem->_message.viewCount.viewCount, [nViewCount intValue])];
[updatedItems addObject:updatedItem];
[updatedItemIndices addObject:@(i)];
}
}
if (markAsSeen) {
markMessagesAsSeen([strongSelf requestPeerId], messageIdToViews.allKeys);
}
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = strongSelf.controller;
int index = -1;
for (NSNumber *nIndex in updatedItemIndices)
{
index++;
[controller updateItemAtIndex:[nIndex intValue] toItem:updatedItems[index] delayAvailability:false];
}
});
}
}];
}
- (void)_messagesDownloaded:(NSArray *)messages
{
NSMutableArray *webPages = [[NSMutableArray alloc] init];
for (TGMessage *message in messages)
{
for (TGMediaAttachment *attachment in message.mediaAttachments)
{
if (attachment.type == TGWebPageMediaAttachmentType)
{
[webPages addObject:attachment];
break;
}
}
}
if (webPages.count != 0)
[self _webPagesUpdated:webPages localIdToWebPage:nil];
}
- (void)_webPagesUpdated:(NSArray *)webPages localIdToWebPage:(NSDictionary *)localIdToWebPage
{
NSMutableArray *updatedItems = [[NSMutableArray alloc] init];
NSMutableArray *atIndices = [[NSMutableArray alloc] init];
NSInteger itemIndex = -1;
for (TGMessageModernConversationItem *item in _items)
{
itemIndex++;
NSInteger index = -1;
for (TGMediaAttachment *attachment in item->_message.mediaAttachments)
{
index++;
if (attachment.type == TGWebPageMediaAttachmentType)
{
int64_t webPageId = ((TGWebPageMediaAttachment *)attachment).webPageId;
if (webPageId != 0) {
for (TGWebPageMediaAttachment *webPage in webPages)
{
if (webPage.webPageId == webPageId)
{
TGMessageModernConversationItem *updatedItem = [item copy];
updatedItem->_message = [updatedItem->_message copy];
NSMutableArray *attachments = [[NSMutableArray alloc] initWithArray:updatedItem->_message.mediaAttachments];
attachments[index] = webPage;
updatedItem->_message.mediaAttachments = attachments;
[TGDatabaseInstance() updateMessage:item->_message.mid peerId:item->_message.cid flags:std::vector<TGDatabaseMessageFlagValue>() media:attachments dispatch:false];
[updatedItems addObject:updatedItem];
[atIndices addObject:@(itemIndex)];
break;
}
}
} else if (((TGWebPageMediaAttachment *)attachment).webPageLocalId != 0) {
TGWebPageMediaAttachment *webPage = localIdToWebPage[@(((TGWebPageMediaAttachment *)attachment).webPageLocalId)];
if (webPage != nil)
{
TGMessageModernConversationItem *updatedItem = [item copy];
updatedItem->_message = [updatedItem->_message copy];
NSMutableArray *attachments = [[NSMutableArray alloc] initWithArray:updatedItem->_message.mediaAttachments];
attachments[index] = webPage;
updatedItem->_message.mediaAttachments = attachments;
[TGDatabaseInstance() updateMessage:item->_message.mid peerId:item->_message.cid flags:std::vector<TGDatabaseMessageFlagValue>() media:attachments dispatch:false];
[updatedItems addObject:updatedItem];
[atIndices addObject:@(itemIndex)];
}
}
break;
}
}
}
if (updatedItems.count != 0)
{
for (NSUInteger i = 0; i < updatedItems.count; i++)
{
[((NSMutableArray *)_items) replaceObjectAtIndex:[atIndices[i] unsignedIntegerValue] withObject:updatedItems[i]];
}
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
for (NSNumber *nIndex in atIndices) {
[indexSet addIndex:[nIndex intValue]];
}
[self _updateMediaStatusDataForItemsInIndexSet:indexSet animated:false forceforceCheckDownload:true];
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = _controller;
int index = -1;
for (TGMessageModernConversationItem *messageItem in updatedItems)
{
index++;
[controller updateItemAtIndex:[atIndices[index] unsignedIntegerValue] toItem:messageItem delayAvailability:false];
}
});
}
}
- (void)navigateToMessageSearch
{
}
- (bool)isASingleBotGroup
{
return false;
}
- (void)_controllerDidUpdateVisibleHoles:(NSArray *)__unused holes
{
}
- (void)_controllerDidUpdateVisibleUnseenMessageIds:(NSArray *)unseenMessageIds {
[TGModernConversationCompanion dispatchOnMessageQueue:^{
NSMutableArray *consumeIds = [[NSMutableArray alloc] init];
for (NSNumber *nMid in unseenMessageIds) {
if (_messageViewsRequested.find([nMid intValue]) == _messageViewsRequested.end()) {
_messageViewsRequested.insert([nMid intValue]);
[consumeIds addObject:nMid];
}
}
if (consumeIds.count != 0) {
if (_messageViewsRequestedBuffer == nil) {
_messageViewsRequestedBuffer = [[NSMutableArray alloc] init];
}
[_messageViewsRequestedBuffer addObjectsFromArray:consumeIds];
}
[_messageViewsRequestedBufferTimer invalidate];
[_messageViewsRequestedBufferTimer start];
}];
}
- (bool)_controllerShouldHideInputTextByDefault
{
return false;
}
- (bool)canDeleteMessage:(TGMessage *)__unused message
{
return true;
}
- (bool)canEditMessage:(TGMessage *)__unused message {
return false;
}
- (bool)canDeleteMessages
{
return true;
}
- (bool)canDeleteAllMessages
{
return true;
}
- (int64_t)requestPeerId {
return 0;
}
- (int64_t)attachedPeerId {
return 0;
}
- (int64_t)requestAccessHash {
return 0;
}
- (void)_toggleBroadcastMode {
}
- (void)_toggleTitleMode {
}
- (void)consumeRequestedMessages {
[TGModernConversationCompanion dispatchOnMessageQueue:^{
NSArray *consumeIds = [[NSArray alloc] initWithArray:_messageViewsRequestedBuffer];
[_messageViewsRequestedBuffer removeAllObjects];
if (consumeIds.count != 0) {
[[TGChannelManagementSignals consumeMessages:[self requestPeerId] accessHash:[self requestAccessHash] messageIds:consumeIds] startWithNext:^(NSDictionary *messageIdToViews) {
[TGModernConversationCompanion dispatchOnMessageQueue:^{
__strong TGModernConversationCompanion *strongSelf = self;
if (strongSelf != nil) {
[strongSelf updateMessageViews:messageIdToViews markAsSeen:true];
}
}];
}];
}
}];
}
- (SSignal *)inputPlaceholderForText:(NSString *)__unused text {
return nil;
}
- (SSignalQueue *)mediaUploadQueue
{
return _mediaUploadQueue;
}
- (id)playlistMetadata:(bool)__unused voice {
return nil;
}
- (void)maybeAskForSecretWebpages {
if (!TGAppDelegateInstance.allowSecretWebpages && !TGAppDelegateInstance.allowSecretWebpagesInitialized && !_askedForSecretPages) {
_askedForSecretPages = true;
__weak TGModernConversationCompanion *weakSelf = self;
TGDispatchOnMainThread(^{
[[[TGAlertView alloc] initWithTitle:nil message:TGLocalized(@"Conversation.SecretLinkPreviewAlert") cancelButtonTitle:TGLocalized(@"Common.No") okButtonTitle:TGLocalized(@"Common.Yes") completionBlock:^(bool okButtonPressed) {
TGAppDelegateInstance.allowSecretWebpagesInitialized = true;
TGAppDelegateInstance.allowSecretWebpages = okButtonPressed;
[TGAppDelegateInstance saveSettings];
if (okButtonPressed) {
[TGModernConversationCompanion dispatchOnMessageQueue:^{
[weakSelf _itemsUpdated];
}];
TGDispatchOnMainThread(^{
TGModernConversationController *controller = [weakSelf controller];
[controller updateWebpageLinks];
});
}
}] show];
});
}
}
- (void)maybeAskForInlineBots {
if (!TGAppDelegateInstance.secretInlineBotsInitialized) {
TGDispatchOnMainThread(^{
TGAppDelegateInstance.secretInlineBotsInitialized = true;
[TGAppDelegateInstance saveSettings];
[[[TGAlertView alloc] initWithTitle:nil message:TGLocalized(@"Conversation.SecretChatContextBotAlert") cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show];
});
}
}
- (SSignal *)editingContextForMessageWithId:(int32_t)__unused messageId {
return [SSignal single:nil];
}
- (SSignal *)saveEditedMessageWithId:(int32_t)__unused messageId text:(NSString *)__unused text disableLinkPreviews:(bool)__unused disableLinkPreviews {
return [SSignal complete];
}
- (bool)canCreateLinksToMessages {
return false;
}
@end