2015-10-01 18:19:52 +02:00
|
|
|
#import "TGNeoConversationController.h"
|
|
|
|
#import "TGNeoChatsController.h"
|
|
|
|
|
|
|
|
#import "TGStringUtils.h"
|
2016-02-25 01:03:51 +01:00
|
|
|
#import "TGDateUtils.h"
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
#import "WKInterfaceTable+TGDataDrivenTable.h"
|
|
|
|
#import "TGTableDeltaUpdater.h"
|
|
|
|
#import "TGInterfaceMenu.h"
|
|
|
|
|
|
|
|
#import "TGBridgeClient.h"
|
|
|
|
#import "TGBridgeContext.h"
|
|
|
|
#import "TGBridgeUser.h"
|
|
|
|
#import "TGBridgeChat.h"
|
|
|
|
#import "TGBridgeChatMessageListView.h"
|
|
|
|
#import "TGBridgeMessage+TGTableItem.h"
|
|
|
|
#import "TGBridgeBotInfo.h"
|
|
|
|
#import "TGBridgeBotReplyMarkup.h"
|
|
|
|
#import "TGBridgeUserCache.h"
|
|
|
|
|
|
|
|
#import "TGChatInfo.h"
|
|
|
|
|
|
|
|
#import "TGBridgeChatMessageListSignals.h"
|
|
|
|
#import "TGBridgeConversationSignals.h"
|
|
|
|
#import "TGBridgePeerSettingsSignals.h"
|
|
|
|
#import "TGBridgeSendMessageSignals.h"
|
|
|
|
#import "TGBridgeBotSignals.h"
|
|
|
|
#import "TGBridgeStateSignal.h"
|
|
|
|
#import "TGBridgeRemoteSignals.h"
|
2016-02-25 01:03:51 +01:00
|
|
|
#import "TGBridgeAudioSignals.h"
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
#import "TGNeoConversationRowController.h"
|
|
|
|
#import "TGNeoConversationStaticRowController.h"
|
2016-02-25 01:03:51 +01:00
|
|
|
#import "TGNeoConversationTimeRowController.h"
|
2015-10-01 18:19:52 +02:00
|
|
|
#import "TGConversationFooterController.h"
|
|
|
|
|
|
|
|
#import "TGUserInfoController.h"
|
|
|
|
#import "TGGroupInfoController.h"
|
|
|
|
#import "TGBotCommandController.h"
|
|
|
|
#import "TGBotKeyboardController.h"
|
|
|
|
#import "TGStickersController.h"
|
|
|
|
#import "TGLocationController.h"
|
|
|
|
#import "TGInputController.h"
|
|
|
|
#import "TGMessageViewController.h"
|
2016-02-25 01:03:51 +01:00
|
|
|
#import "TGAudioMicAlertController.h"
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
NSString *const TGNeoConversationControllerIdentifier = @"TGNeoConversationController";
|
|
|
|
const NSInteger TGNeoConversationControllerDefaultBatchLimit = 8;
|
|
|
|
const NSInteger TGNeoConversationControllerMaximumBatchLimit = 20;
|
|
|
|
|
|
|
|
const NSInteger TGNeoConversationControllerInitialRenderCount = 4;
|
|
|
|
|
|
|
|
@interface TGNeoConversationControllerContext ()
|
|
|
|
{
|
|
|
|
int64_t _peerId;
|
|
|
|
SVariable *_messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) SSignal *signal;
|
|
|
|
@property (nonatomic, readonly) bool shouldReadMessages;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation TGNeoConversationControllerContext
|
|
|
|
|
|
|
|
- (instancetype)initWithChat:(TGBridgeChat *)chat
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil)
|
|
|
|
{
|
|
|
|
_chat = chat;
|
|
|
|
|
|
|
|
[self initialize];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithPeerId:(int64_t)peerId
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil)
|
|
|
|
{
|
|
|
|
_peerId = peerId;
|
|
|
|
|
|
|
|
[self initialize];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)initialize
|
|
|
|
{
|
|
|
|
_shouldReadMessages = true;
|
|
|
|
|
|
|
|
NSInteger rangeCount = TGNeoConversationControllerDefaultBatchLimit;
|
|
|
|
NSInteger initialUnreadCount = _chat.unreadCount;
|
|
|
|
|
|
|
|
if (initialUnreadCount > 0)
|
|
|
|
{
|
|
|
|
rangeCount = MAX(TGNeoConversationControllerDefaultBatchLimit, MIN(TGNeoConversationControllerMaximumBatchLimit, initialUnreadCount));
|
|
|
|
|
|
|
|
if (initialUnreadCount > TGNeoConversationControllerMaximumBatchLimit)
|
|
|
|
_shouldReadMessages = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_messages = [[SVariable alloc] init];
|
|
|
|
[_messages set:[[TGBridgeChatMessageListSignals chatMessageListViewWithPeerId:self.peerId atMessageId:0 rangeMessageCount:rangeCount] deliverOn:[SQueue mainQueue]]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (int64_t)peerId
|
|
|
|
{
|
|
|
|
if (_peerId == 0)
|
|
|
|
return _chat.identifier;
|
|
|
|
|
|
|
|
return _peerId;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (SSignal *)signal
|
|
|
|
{
|
|
|
|
return _messages.signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@interface TGNeoConversationController () <TGTableDataSource>
|
|
|
|
{
|
|
|
|
TGNeoConversationControllerContext *_context;
|
|
|
|
|
|
|
|
SMetaDisposable *_messagesListDisposable;
|
|
|
|
SMetaDisposable *_chatGroupDisposable;
|
|
|
|
SMetaDisposable *_sendMessageDisposable;
|
|
|
|
SMetaDisposable *_readMessagesDisposable;
|
|
|
|
SMetaDisposable *_peerSettingsDisposable;
|
|
|
|
SMetaDisposable *_updateSettingsDisposable;
|
|
|
|
SMetaDisposable *_botInfoDisposable;
|
|
|
|
SMetaDisposable *_botReplyMarkupDisposable;
|
|
|
|
SMetaDisposable *_remoteActionDisposable;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
SMetaDisposable *_sentMediaDisposable;
|
|
|
|
SMetaDisposable *_playAudioDisposable;
|
|
|
|
TGBridgeMediaAttachment *_pendingAudioAttachment;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
TGBridgeChat *_chatModel;
|
|
|
|
TGBridgeChatMessageListView *_messageListView;
|
|
|
|
TGBridgeBotInfo *_botInfo;
|
|
|
|
TGBridgeBotReplyMarkup *_botReplyMarkup;
|
2016-02-25 01:03:51 +01:00
|
|
|
NSDictionary *_peerModels;
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
NSMutableArray *_pendingSentMessages;
|
|
|
|
bool _shouldReadMessages;
|
|
|
|
|
|
|
|
bool _muted;
|
|
|
|
bool _blocked;
|
|
|
|
bool _hasBots;
|
|
|
|
|
|
|
|
NSArray *_rowModels;
|
|
|
|
|
|
|
|
bool _initialized;
|
|
|
|
bool _initialRendering;
|
|
|
|
bool _shouldScrollToBottom;
|
|
|
|
TGInterfaceMenu *_menu;
|
|
|
|
TGConversationFooterOptions _footerOptions;
|
|
|
|
bool _dontAnimateFooterTransition;
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation TGNeoConversationController
|
|
|
|
|
|
|
|
- (instancetype)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil)
|
|
|
|
{
|
|
|
|
_messagesListDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_chatGroupDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_sendMessageDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_readMessagesDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_peerSettingsDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_updateSettingsDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_botInfoDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_botReplyMarkupDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_remoteActionDisposable = [[SMetaDisposable alloc] init];
|
2016-02-25 01:03:51 +01:00
|
|
|
_sentMediaDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_playAudioDisposable = [[SMetaDisposable alloc] init];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
_pendingSentMessages = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
_dontAnimateFooterTransition = true;
|
|
|
|
|
|
|
|
self.table.reloadDataReversed = true;
|
|
|
|
self.table.tableDataSource = self;
|
|
|
|
[self.table _setInitialHidden:true];
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(synchronizationStateUpdated:) name:TGSynchronizationStateNotification object:nil];
|
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextUpdated:) name:TGContextNotification object:nil];
|
2015-10-01 18:19:52 +02:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[_messagesListDisposable dispose];
|
|
|
|
[_chatGroupDisposable dispose];
|
|
|
|
[_sendMessageDisposable dispose];
|
|
|
|
[_readMessagesDisposable dispose];
|
|
|
|
[_peerSettingsDisposable dispose];
|
|
|
|
[_updateSettingsDisposable dispose];
|
|
|
|
[_botInfoDisposable dispose];
|
|
|
|
[_botReplyMarkupDisposable dispose];
|
|
|
|
[_remoteActionDisposable dispose];
|
2016-02-25 01:03:51 +01:00
|
|
|
[_sentMediaDisposable dispose];
|
|
|
|
[_playAudioDisposable dispose];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)configureWithContext:(TGNeoConversationControllerContext *)context
|
|
|
|
{
|
|
|
|
_context = context;
|
|
|
|
|
|
|
|
if (context.finished != nil)
|
|
|
|
context.finished();
|
|
|
|
|
|
|
|
if (_context.chat.identifier < 0)
|
|
|
|
_chatModel = _context.chat;
|
|
|
|
|
|
|
|
self.title = [self conversationTitle];
|
|
|
|
|
|
|
|
_shouldReadMessages = context.shouldReadMessages;
|
|
|
|
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
[_messagesListDisposable setDisposable:[context.signal startWithNext:^(NSDictionary *models)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_shouldScrollToBottom = (strongSelf->_messageListView == nil);
|
|
|
|
|
|
|
|
TGBridgeChatMessageListView *messageListView = models[TGBridgeChatMessageListViewKey];
|
|
|
|
strongSelf->_messageListView = messageListView;
|
2016-02-25 01:03:51 +01:00
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
[[TGBridgeUserCache instance] storeUsers:[models[TGBridgeUsersDictionaryKey] allValues]];
|
2016-02-25 01:03:51 +01:00
|
|
|
strongSelf->_peerModels = models[TGBridgeUsersDictionaryKey];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
[strongSelf _readMessagesIfNeeded];
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsAnyGroup])
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
[_chatGroupDisposable setDisposable:[[TGBridgeConversationSignals conversationWithPeerId:[self peerId]] startWithNext:^(NSDictionary *next)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_chatModel = next[TGBridgeChatKey];
|
|
|
|
[[TGBridgeUserCache instance] storeUsers:[next[TGBridgeUsersDictionaryKey] allValues]];
|
|
|
|
|
|
|
|
[strongSelf _updateBots];
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self _updateBots];
|
|
|
|
if (_hasBots)
|
|
|
|
{
|
|
|
|
[_botInfoDisposable setDisposable:[[TGBridgeBotSignals botInfoForUserId:(int32_t)[self peerId]] startWithNext:^(TGBridgeBotInfo *next)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_botInfo = next;
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsAnyGroup] || _hasBots)
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
[_botReplyMarkupDisposable setDisposable:[[TGBridgeBotSignals botReplyMarkupForPeerId:[self peerId]] startWithNext:^(TGBridgeBotReplyMarkup *next)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_botReplyMarkup = next;
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
}
|
|
|
|
|
|
|
|
[_peerSettingsDisposable setDisposable:[[TGBridgePeerSettingsSignals peerSettingsWithPeerId:[self peerId]] startWithNext:^(NSDictionary *next)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TGBridgePeerNotificationSettings *settings = next[@"notifications"];
|
|
|
|
bool blocked = [next[@"blocked"] boolValue];
|
|
|
|
bool muted = (settings.muteFor > 0);
|
|
|
|
|
|
|
|
strongSelf->_blocked = blocked;
|
|
|
|
strongSelf->_muted = muted;
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
[_sentMediaDisposable setDisposable:[[TGBridgeAudioSignals sentAudioForConversationId:[self peerId]] startWithNext:^(id next)
|
|
|
|
{
|
|
|
|
|
|
|
|
}]];
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
[self configureHandoff];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)reloadData
|
|
|
|
{
|
|
|
|
NSArray *currentRowModels = _rowModels;
|
|
|
|
NSMutableArray *rowModels = [TGNeoConversationController reversedMessagesArray:_messageListView.messages];
|
|
|
|
if (rowModels == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TGConversationFooterOptions oldFooterOptions = _footerOptions;
|
|
|
|
|
|
|
|
_footerOptions = TGConversationFooterOptionsSendMessage;
|
|
|
|
if (_chatModel.isKickedFromGroup || _chatModel.hasLeftGroup)
|
|
|
|
_footerOptions = TGConversationFooterOptionsInactive;
|
|
|
|
else if (_blocked)
|
|
|
|
_footerOptions = [self _userIsBot] ? TGConversationFooterOptionsRestartBot : TGConversationFooterOptionsUnblock;
|
|
|
|
else if (![self peerIsGroup] && _hasBots && _messageListView != nil && _messageListView.messages.count == 0)
|
|
|
|
_footerOptions = TGConversationFooterOptionsStartBot;
|
|
|
|
|
|
|
|
if (_footerOptions == TGConversationFooterOptionsSendMessage && _hasBots)
|
|
|
|
{
|
|
|
|
if (_botReplyMarkup.rows.count > 0)
|
|
|
|
_footerOptions |= TGConversationFooterOptionsBotKeyboard;
|
|
|
|
else
|
|
|
|
_footerOptions |= TGConversationFooterOptionsBotCommands;
|
|
|
|
}
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsAnyGroup] || !_hasBots)
|
2015-10-01 18:19:52 +02:00
|
|
|
_footerOptions |= TGConversationFooterOptionsVoice;
|
|
|
|
|
|
|
|
NSMutableArray *pendingSentMessages = [[NSMutableArray alloc] init];
|
|
|
|
for (TGBridgeMessage *message in _pendingSentMessages)
|
|
|
|
{
|
2016-02-25 01:03:51 +01:00
|
|
|
TGBridgeDocumentMediaAttachment *documentAttachment = nil;
|
2015-10-01 18:19:52 +02:00
|
|
|
TGBridgeLocationMediaAttachment *locationAttachment = nil;
|
|
|
|
TGBridgeAudioMediaAttachment *audioAttachment = nil;
|
|
|
|
|
|
|
|
for (TGBridgeMediaAttachment *attachment in message.media)
|
|
|
|
{
|
|
|
|
if ([attachment isKindOfClass:[TGBridgeDocumentMediaAttachment class]])
|
2016-02-25 01:03:51 +01:00
|
|
|
documentAttachment = (TGBridgeDocumentMediaAttachment *)attachment;
|
2015-10-01 18:19:52 +02:00
|
|
|
else if ([attachment isKindOfClass:[TGBridgeLocationMediaAttachment class]])
|
|
|
|
locationAttachment = (TGBridgeLocationMediaAttachment *)attachment;
|
|
|
|
else if ([attachment isKindOfClass:[TGBridgeAudioMediaAttachment class]])
|
|
|
|
audioAttachment = (TGBridgeAudioMediaAttachment *)attachment;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool skip = false;
|
|
|
|
|
|
|
|
for (TGBridgeMessage *realMessage in rowModels)
|
|
|
|
{
|
|
|
|
if (!realMessage.outgoing)
|
|
|
|
continue;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if (fabs(realMessage.date - message.date) > 4.0)
|
2015-10-01 18:19:52 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if ([realMessage.text isEqualToString:message.text])
|
|
|
|
{
|
|
|
|
skip = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-02-25 01:03:51 +01:00
|
|
|
TGBridgeDocumentMediaAttachment *realDocumentAttachment = nil;
|
2015-10-01 18:19:52 +02:00
|
|
|
TGBridgeLocationMediaAttachment *realLocationAttachment = nil;
|
|
|
|
TGBridgeAudioMediaAttachment *realAudioAttachment = nil;
|
|
|
|
|
|
|
|
for (TGBridgeMediaAttachment *attachment in message.media)
|
|
|
|
{
|
|
|
|
if ([attachment isKindOfClass:[TGBridgeDocumentMediaAttachment class]])
|
2016-02-25 01:03:51 +01:00
|
|
|
realDocumentAttachment = (TGBridgeDocumentMediaAttachment *)attachment;
|
2015-10-01 18:19:52 +02:00
|
|
|
else if ([attachment isKindOfClass:[TGBridgeLocationMediaAttachment class]])
|
|
|
|
realLocationAttachment = (TGBridgeLocationMediaAttachment *)attachment;
|
|
|
|
else if ([attachment isKindOfClass:[TGBridgeAudioMediaAttachment class]])
|
|
|
|
realAudioAttachment = (TGBridgeAudioMediaAttachment *)attachment;
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([realDocumentAttachment isEqual:documentAttachment] || [realLocationAttachment isEqual:locationAttachment] || [realAudioAttachment isEqual:audioAttachment])
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
skip = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!skip)
|
|
|
|
[pendingSentMessages addObject:message];
|
|
|
|
}
|
|
|
|
_pendingSentMessages = pendingSentMessages;
|
|
|
|
|
|
|
|
for (TGBridgeMessage *message in pendingSentMessages)
|
|
|
|
[rowModels addObject:message];
|
|
|
|
|
|
|
|
if (_botInfo.botDescription.length > 0)
|
|
|
|
{
|
|
|
|
TGChatInfo *chatInfo = [[TGChatInfo alloc] init];
|
2016-02-25 01:03:51 +01:00
|
|
|
chatInfo.title = TGLocalized(@"Bot.DescriptionTitle");
|
2015-10-01 18:19:52 +02:00
|
|
|
chatInfo.text = _botInfo.botDescription;
|
|
|
|
[rowModels insertObject:chatInfo atIndex:0];
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
_rowModels = [TGNeoConversationController timestampedModelsArray:rowModels];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
bool initial = (currentRowModels == nil);
|
|
|
|
if (!initial)
|
|
|
|
{
|
|
|
|
[TGTableDeltaUpdater updateTable:self.table oldData:currentRowModels newData:_rowModels controllerClassForIndexPath:^Class(TGIndexPath *indexPath)
|
|
|
|
{
|
|
|
|
return [self table:self.table rowControllerClassAtIndexPath:indexPath];
|
|
|
|
}];
|
|
|
|
|
|
|
|
if (oldFooterOptions != _footerOptions)
|
|
|
|
[self.table reloadFooter];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_initialRendering = true;
|
|
|
|
|
|
|
|
[self.table reloadData];
|
|
|
|
self.activityIndicator.hidden = true;
|
|
|
|
self.table.hidden = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_shouldScrollToBottom)
|
|
|
|
{
|
|
|
|
_shouldScrollToBottom = false;
|
|
|
|
|
|
|
|
if (!_initialized)
|
|
|
|
{
|
|
|
|
[self animateWithDuration:0.4 animations:^
|
|
|
|
{
|
|
|
|
self.table.alpha = 1.0f;
|
|
|
|
}];
|
|
|
|
|
|
|
|
_initialized = true;
|
|
|
|
}
|
|
|
|
[self.table scrollToRowAtIndexPath:[TGIndexPath indexPathForRow:_rowModels.count - 1 inSection:0]];
|
|
|
|
|
|
|
|
if (_initialRendering)
|
|
|
|
{
|
|
|
|
TGDispatchAfter(1.7, dispatch_get_main_queue(), ^
|
|
|
|
{
|
|
|
|
_initialRendering = false;
|
|
|
|
[self.table reloadAllRows];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[self updateMenuItems];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)conversationTitle
|
|
|
|
{
|
|
|
|
if ([self peerIsGroup] || [self peerIsChannel])
|
|
|
|
return _chatModel.groupTitle;
|
|
|
|
else
|
|
|
|
return [[[TGBridgeUserCache instance] userWithId:(int32_t)[self peerId]] displayName];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)configureHandoff
|
|
|
|
{
|
|
|
|
int64_t peerId = [self peerId];
|
2016-02-25 01:03:51 +01:00
|
|
|
bool isGroup = [self peerIsGroup] || [self peerIsChannel];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
if (isGroup)
|
|
|
|
peerId = -peerId;
|
|
|
|
|
|
|
|
NSMutableDictionary *peerDict = [[NSMutableDictionary alloc] init];
|
|
|
|
peerDict[@"type"] = isGroup ? @"group" : @"user";
|
|
|
|
peerDict[@"id"] = @(peerId);
|
|
|
|
|
|
|
|
NSDictionary *userInfo = @{@"user_id": @(_context.context.userId), @"peer": peerDict};
|
|
|
|
[self updateUserActivity:@"org.telegram.conversation" userInfo:userInfo webpageURL:[NSURL URLWithString:@"https://telegram.org/dl"]];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
- (void)synchronizationStateUpdated:(NSNotification *)notification
|
|
|
|
{
|
2016-02-25 01:03:51 +01:00
|
|
|
//TGBridgeSynchronizationStateValue value = (TGBridgeSynchronizationStateValue)[notification.userInfo[TGSynchronizationStateKey] integerValue];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
//if (self.isVisible)
|
|
|
|
// [self updateTitleWithState:value];
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
- (void)contextUpdated:(NSNotification *)notification
|
|
|
|
{
|
|
|
|
TGBridgeContext *context = notification.userInfo[TGContextNotificationKey];
|
|
|
|
if (context != nil)
|
|
|
|
_context.context = context;
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
- (void)updateTitleWithState:(TGBridgeSynchronizationStateValue)value
|
|
|
|
{
|
|
|
|
NSString *state = [TGNeoChatsController stringForSyncState:value];
|
|
|
|
if (_context.context == nil || state == nil)
|
|
|
|
self.title = [self conversationTitle];
|
|
|
|
else
|
|
|
|
self.title = state;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateMenuItems
|
|
|
|
{
|
|
|
|
[_menu clearItems];
|
|
|
|
|
|
|
|
if (_context.chat.isKickedFromGroup || _context.chat.hasLeftGroup)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (_menu == nil)
|
|
|
|
_menu = [[TGInterfaceMenu alloc] initForInterfaceController:self];
|
|
|
|
|
|
|
|
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
2016-02-25 01:03:51 +01:00
|
|
|
TGInterfaceMenuItem *infoItem = [[TGInterfaceMenuItem alloc] initWithItemIcon:WKMenuItemIconInfo title:[self peerIsAnyGroup] ? TGLocalized(@"Watch.Conversation.GroupInfo") : TGLocalized(@"Watch.Conversation.UserInfo") actionBlock:^(TGInterfaceController *controller, TGInterfaceMenuItem *sender)
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ([strongSelf peerIsGroup])
|
|
|
|
{
|
|
|
|
TGGroupInfoControllerContext *context = [[TGGroupInfoControllerContext alloc] initWithGroupChat:strongSelf->_context.chat];
|
|
|
|
[controller pushControllerWithClass:[TGGroupInfoController class] context:context];
|
|
|
|
}
|
|
|
|
else if ([strongSelf peerIsChannel])
|
|
|
|
{
|
|
|
|
TGUserInfoControllerContext *context = [[TGUserInfoControllerContext alloc] initWithChannel:strongSelf->_chatModel];
|
|
|
|
context.disallowCompose = true;
|
|
|
|
[controller pushControllerWithClass:[TGUserInfoController class] context:context];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TGUserInfoControllerContext *context = [[TGUserInfoControllerContext alloc] initWithUserId:(int32_t)[strongSelf peerId]];
|
|
|
|
context.disallowCompose = true;
|
|
|
|
[controller pushControllerWithClass:[TGUserInfoController class] context:context];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
[menuItems addObject:infoItem];
|
|
|
|
|
|
|
|
bool muted = _muted;
|
|
|
|
bool blocked = _blocked;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
bool muteForever = [self peerIsAnyGroup];
|
|
|
|
int32_t muteFor = muteForever ? INT_MAX : 1;
|
|
|
|
NSString *muteTitle = muteForever ? TGLocalized(@"Watch.UserInfo.Mute") : [NSString stringWithFormat:TGLocalized([TGStringUtils integerValueFormat:@"Watch.UserInfo.Mute_" value:muteFor]), muteFor];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
TGInterfaceMenuItem *muteItem = [[TGInterfaceMenuItem alloc] initWithItemIcon:muted ? WKMenuItemIconSpeaker : WKMenuItemIconMute title:muted ? TGLocalized(@"Watch.UserInfo.Unmute") : muteTitle actionBlock:^(TGInterfaceController *controller, TGInterfaceMenuItem *sender)
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TGBridgePeerNotificationSettings *settings = [[TGBridgePeerNotificationSettings alloc] init];
|
2016-02-25 01:03:51 +01:00
|
|
|
settings.muteFor = muted ? 0 : (muteFor == INT_MAX ? INT_MAX : muteFor * 60 * 60);
|
2015-10-01 18:19:52 +02:00
|
|
|
[strongSelf->_updateSettingsDisposable setDisposable:[[[TGBridgePeerSettingsSignals updateNotificationSettingsWithPeerId:[strongSelf peerId] settings:settings] deliverOn:[SQueue mainQueue]] startWithNext:nil completed:^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_muted = !muted;
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
}];
|
|
|
|
[menuItems addObject:muteItem];
|
|
|
|
|
|
|
|
if (![self peerIsGroup] && ![self peerIsChannel])
|
|
|
|
{
|
2016-02-25 01:03:51 +01:00
|
|
|
TGInterfaceMenuItem *blockItem = [[TGInterfaceMenuItem alloc] initWithItemIcon:WKMenuItemIconBlock title:blocked ? TGLocalized(@"Watch.UserInfo.Unblock") : TGLocalized(@"Watch.UserInfo.Block") actionBlock:^(TGInterfaceController *controller, TGInterfaceMenuItem *sender)
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf->_updateSettingsDisposable setDisposable:[[[TGBridgePeerSettingsSignals updateBlockStatusWithPeerId:[strongSelf peerId] blocked:!blocked] deliverOn:[SQueue mainQueue]] startWithNext:nil completed:^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_blocked = !blocked;
|
|
|
|
|
|
|
|
[strongSelf performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
}]];
|
|
|
|
}];
|
|
|
|
[menuItems addObject:blockItem];
|
|
|
|
}
|
|
|
|
|
|
|
|
[_menu addItems:menuItems];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Peer
|
|
|
|
|
|
|
|
- (int64_t)peerId
|
|
|
|
{
|
|
|
|
return _context.peerId;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (bool)peerIsGroup
|
|
|
|
{
|
|
|
|
if (_chatModel != nil)
|
|
|
|
return _chatModel.isGroup;
|
|
|
|
else
|
|
|
|
return _context.peerId < 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (bool)peerIsChannel
|
|
|
|
{
|
|
|
|
if (_chatModel != nil)
|
|
|
|
return _chatModel.isChannel;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
- (bool)peerIsChannelGroup
|
|
|
|
{
|
|
|
|
if (_chatModel != nil)
|
|
|
|
return _chatModel.isChannelGroup;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (bool)peerIsAnyGroup
|
|
|
|
{
|
|
|
|
return [self peerIsGroup] || [self peerIsChannelGroup];
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
#pragma mark - Bots
|
|
|
|
|
|
|
|
- (SSignal *)botCommandListSignal
|
|
|
|
{
|
|
|
|
if (!_hasBots)
|
|
|
|
return nil;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsAnyGroup])
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
NSMutableArray *botInfoSignals = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableArray *botUsers = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableArray *initialStates = [[NSMutableArray alloc] init];
|
|
|
|
[_chatModel.participantsUserIds enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop)
|
|
|
|
{
|
|
|
|
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:(int32_t)idx];
|
|
|
|
if ([user isBot])
|
|
|
|
{
|
|
|
|
[botUsers addObject:user];
|
|
|
|
[initialStates addObject:@[]];
|
|
|
|
[botInfoSignals addObject:[[TGBridgeBotSignals botInfoForUserId:user.identifier] map:^NSArray *(TGBridgeBotInfo *botInfo)
|
|
|
|
{
|
|
|
|
if (botInfo.commandList == nil)
|
|
|
|
return @[];
|
|
|
|
|
|
|
|
return botInfo.commandList;
|
|
|
|
}]];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
return [[SSignal combineSignals:botInfoSignals withInitialStates:initialStates] map:^id(NSArray *commandLists)
|
|
|
|
{
|
|
|
|
NSMutableArray *commands = [[NSMutableArray alloc] init];
|
|
|
|
NSInteger index = 0;
|
|
|
|
for (NSArray *commandList in commandLists)
|
|
|
|
{
|
|
|
|
[commands addObject:@{ TGBotCommandUserKey: botUsers[index], TGBotCommandListKey: commandList } ];
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return commands;
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
else if ([self _userIsBot])
|
|
|
|
{
|
|
|
|
int32_t userId = (int32_t)[self peerId];
|
|
|
|
return [[TGBridgeBotSignals botInfoForUserId:userId] map:^NSArray *(TGBridgeBotInfo *botInfo)
|
|
|
|
{
|
|
|
|
if (botInfo != nil)
|
|
|
|
{
|
|
|
|
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:userId];
|
|
|
|
return @[ @{ TGBotCommandUserKey: user, TGBotCommandListKey: botInfo.commandList } ];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (bool)_userIsBot
|
|
|
|
{
|
|
|
|
if ([self peerId] < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:(int32_t)[self peerId]];
|
|
|
|
return [user isBot];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_updateBots
|
|
|
|
{
|
|
|
|
_hasBots = false;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsAnyGroup])
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
[_chatModel.participantsUserIds enumerateIndexesUsingBlock:^(NSUInteger userId, BOOL * _Nonnull stop)
|
|
|
|
{
|
|
|
|
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:(int32_t)userId];
|
|
|
|
if ([user isBot])
|
|
|
|
{
|
|
|
|
_hasBots = true;
|
|
|
|
*stop = true;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TGBridgeUser *user = [[TGBridgeUserCache instance] userWithId:(int32_t)[self peerId]];
|
|
|
|
_hasBots = [user isBot];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
- (void)sendMessageWithText:(NSString *)text
|
|
|
|
{
|
|
|
|
[self sendMessageWithText:text replyToMessage:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendMessageWithText:(NSString *)text replyToMessage:(TGBridgeMessage *)replyToMessage
|
|
|
|
{
|
|
|
|
_shouldReadMessages = true;
|
|
|
|
_shouldScrollToBottom = true;
|
|
|
|
|
|
|
|
[_pendingSentMessages addObject:[TGBridgeMessage temporaryNewMessageForText:text userId:_context.context.userId replyToMessage:replyToMessage]];
|
|
|
|
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
[self performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
|
|
|
|
[_sendMessageDisposable setDisposable:[[[TGBridgeSendMessageSignals sendMessageWithPeerId:[self peerId] text:text replyToMid:0] deliverOn:[SQueue mainQueue]] startWithNext:nil]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendMessageWithStickerAttachment:(TGBridgeDocumentMediaAttachment *)sticker
|
|
|
|
{
|
|
|
|
_shouldReadMessages = true;
|
|
|
|
_shouldScrollToBottom = true;
|
|
|
|
|
|
|
|
[_pendingSentMessages addObject:[TGBridgeMessage temporaryNewMessageForSticker:sticker userId:_context.context.userId]];
|
|
|
|
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
[self performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
|
|
|
|
[_sendMessageDisposable setDisposable:[[[TGBridgeSendMessageSignals sendMessageWithPeerId:[self peerId] sticker:sticker replyToMid:0] deliverOn:[SQueue mainQueue]] startWithNext:nil]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendMessageWithLocationAttachment:(TGBridgeLocationMediaAttachment *)location
|
|
|
|
{
|
|
|
|
_shouldReadMessages = true;
|
|
|
|
_shouldScrollToBottom = true;
|
|
|
|
|
|
|
|
[_pendingSentMessages addObject:[TGBridgeMessage temporaryNewMessageForLocation:location userId:_context.context.userId]];
|
|
|
|
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
[self performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
|
|
|
|
[_sendMessageDisposable setDisposable:[[[TGBridgeSendMessageSignals sendMessageWithPeerId:[self peerId] location:location replyToMid:0] deliverOn:[SQueue mainQueue]] startWithNext:nil]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendAudioWithUniqueId:(int64_t)uniqueId duration:(int32_t)duration url:(NSURL *)url
|
|
|
|
{
|
|
|
|
_shouldReadMessages = true;
|
|
|
|
_shouldScrollToBottom = true;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
[_pendingSentMessages addObject:[TGBridgeMessage temporaryNewMessageForAudioWithDuration:duration userId:_context.context.userId localAudioId:uniqueId]];
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
[self performInterfaceUpdate:^(bool animated)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}];
|
|
|
|
|
|
|
|
NSDictionary *metadata = @
|
|
|
|
{
|
|
|
|
TGBridgeIncomingFileTypeKey: TGBridgeIncomingFileTypeAudio,
|
2016-02-25 01:03:51 +01:00
|
|
|
TGBridgeIncomingFileRandomIdKey: @(uniqueId),
|
2015-10-01 18:19:52 +02:00
|
|
|
TGBridgeIncomingFilePeerIdKey: @([self peerId]),
|
|
|
|
TGBridgeIncomingFileReplyToMidKey: @(0)
|
|
|
|
};
|
|
|
|
|
|
|
|
[[TGBridgeClient instance] sendFileWithURL:url metadata:metadata];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (TGBridgeMessage *)_latestIncomingMessage
|
|
|
|
{
|
|
|
|
__block TGBridgeMessage *incomingMessage = nil;
|
|
|
|
|
|
|
|
[_messageListView.messages enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TGBridgeMessage *message, NSUInteger index, BOOL *stop)
|
|
|
|
{
|
|
|
|
if (!message.outgoing)
|
|
|
|
{
|
|
|
|
*stop = true;
|
|
|
|
incomingMessage = message;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
return incomingMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_readMessagesIfNeeded
|
|
|
|
{
|
|
|
|
bool hasUnreadMessages = false;
|
|
|
|
for (TGBridgeMessage *message in _messageListView.messages)
|
|
|
|
{
|
|
|
|
if (!message.outgoing && message.unread)
|
|
|
|
{
|
|
|
|
hasUnreadMessages = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasUnreadMessages && _shouldReadMessages)
|
|
|
|
{
|
|
|
|
[_readMessagesDisposable setDisposable:[[TGBridgeChatMessageListSignals readChatMessageListWithPeerId:[self peerId]] startWithNext:nil completed:nil]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Table Data Source & Delegate
|
|
|
|
|
|
|
|
- (Class)table:(WKInterfaceTable *)table rowControllerClassAtIndexPath:(TGIndexPath *)indexPath
|
|
|
|
{
|
|
|
|
id model = _rowModels[indexPath.row];
|
|
|
|
|
|
|
|
if ([model isKindOfClass:[TGBridgeMessage class]])
|
|
|
|
{
|
|
|
|
return [TGNeoRowController rowControllerClassForMessage:(TGBridgeMessage *)model];
|
|
|
|
}
|
|
|
|
else if ([model isKindOfClass:[TGChatInfo class]])
|
|
|
|
{
|
|
|
|
return [TGNeoConversationStaticRowController class];
|
|
|
|
}
|
2016-02-25 01:03:51 +01:00
|
|
|
else if ([model isKindOfClass:[TGChatTimestamp class]])
|
|
|
|
{
|
|
|
|
return [TGNeoConversationTimeRowController class];
|
|
|
|
}
|
2015-10-01 18:19:52 +02:00
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)numberOfRowsInTable:(WKInterfaceTable *)table section:(NSUInteger)section
|
|
|
|
{
|
|
|
|
return _rowModels.count;
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
- (void)table:(WKInterfaceTable *)table updateRowController:(TGTableRowController *)controller forIndexPath:(TGIndexPath *)indexPath
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
|
|
|
|
id model = _rowModels[indexPath.row];
|
|
|
|
NSUInteger index = [self numberOfRowsInTable:self.table section:0] - indexPath.row - 1;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([model isKindOfClass:[TGChatTimestamp class]])
|
|
|
|
{
|
|
|
|
TGNeoConversationTimeRowController *timeController = (TGNeoConversationTimeRowController *)controller;
|
|
|
|
[timeController updateWithTimestamp:model];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TGNeoRowController *rowController = (TGNeoRowController *)controller;
|
|
|
|
rowController.shouldRenderContent = ^bool
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf != nil && strongSelf->_initialRendering && index >= TGNeoConversationControllerInitialRenderCount)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
if ([model isKindOfClass:[TGBridgeMessage class]])
|
|
|
|
{
|
|
|
|
TGBridgeMessage *message = (TGBridgeMessage *)model;
|
|
|
|
|
|
|
|
TGNeoConversationRowController *conversationRow = (TGNeoConversationRowController *)controller;
|
2016-02-25 01:03:51 +01:00
|
|
|
__weak TGNeoConversationRowController *weakConversationRow = conversationRow;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
conversationRow.animate = ^(void (^animations)(void))
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf animateWithDuration:0.25 animations:animations];
|
|
|
|
};
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
conversationRow.buttonPressed = ^
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
TGBridgeMediaAttachment *audioAttachment = nil;
|
|
|
|
for (TGBridgeMediaAttachment *attachment in message.media)
|
|
|
|
{
|
|
|
|
if ([attachment isKindOfClass:[TGBridgeAudioMediaAttachment class]])
|
|
|
|
{
|
|
|
|
audioAttachment = (TGBridgeAudioMediaAttachment *)attachment;
|
|
|
|
}
|
|
|
|
else if ([attachment isKindOfClass:[TGBridgeDocumentMediaAttachment class]])
|
|
|
|
{
|
|
|
|
TGBridgeDocumentMediaAttachment *documentAttachment = (TGBridgeDocumentMediaAttachment *)attachment;
|
|
|
|
if (documentAttachment.isVoice)
|
|
|
|
audioAttachment = documentAttachment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (audioAttachment != nil)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationRowController *strongConversationRow = weakConversationRow;
|
|
|
|
if ([strongSelf->_pendingAudioAttachment isEqual:audioAttachment])
|
|
|
|
{
|
|
|
|
if (strongConversationRow != nil)
|
|
|
|
[strongConversationRow setProcessingState:false];
|
|
|
|
|
|
|
|
strongSelf->_pendingAudioAttachment = nil;
|
|
|
|
|
|
|
|
[strongSelf->_playAudioDisposable setDisposable:nil];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (strongConversationRow != nil)
|
|
|
|
[strongConversationRow setProcessingState:true];
|
|
|
|
|
|
|
|
strongSelf->_pendingAudioAttachment = audioAttachment;
|
|
|
|
|
|
|
|
[strongSelf->_playAudioDisposable setDisposable:[[[TGBridgeAudioSignals audioForAttachment:audioAttachment conversationId:[strongSelf peerId] messageId:message.identifier] deliverOn:[SQueue mainQueue]] startWithNext:^(NSURL *url)
|
|
|
|
{
|
|
|
|
if (url == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
__strong TGNeoConversationRowController *strongConversationRow = weakConversationRow;
|
|
|
|
if (strongConversationRow != nil)
|
|
|
|
[strongConversationRow setProcessingState:false];
|
|
|
|
|
|
|
|
strongSelf->_pendingAudioAttachment = nil;
|
|
|
|
|
|
|
|
[strongSelf presentMediaPlayerControllerWithURL:url options:@{ WKMediaPlayerControllerOptionsAutoplayKey: @true } completion:^(BOOL didPlayToEnd, NSTimeInterval endTime, NSError *error) {}];
|
|
|
|
}]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[strongSelf->_remoteActionDisposable setDisposable:[[TGBridgeRemoteSignals openRemoteMessageWithPeerId:[strongSelf peerId] messageId:message.identifier type:0 autoPlay:true] startWithNext:nil]];
|
|
|
|
}
|
2015-10-01 18:19:52 +02:00
|
|
|
};
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
TGNeoMessageType type = TGNeoMessageTypeGeneric;
|
|
|
|
if ([self peerIsAnyGroup])
|
|
|
|
type = TGNeoMessageTypeGroup;
|
|
|
|
else if ([self peerIsChannel])
|
|
|
|
type = TGNeoMessageTypeChannel;
|
|
|
|
|
|
|
|
conversationRow.additionalPeers = _peerModels;
|
|
|
|
[conversationRow updateWithMessage:message context:_context.context index:index type:type];
|
2015-10-01 18:19:52 +02:00
|
|
|
}
|
|
|
|
else if ([model isKindOfClass:[TGChatInfo class]])
|
|
|
|
{
|
|
|
|
TGChatInfo *chatInfo = (TGChatInfo *)model;
|
|
|
|
|
|
|
|
TGNeoConversationStaticRowController *conversationRow = (TGNeoConversationStaticRowController *)controller;
|
|
|
|
[conversationRow updateWithChatInfo:chatInfo];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndexPath:(TGIndexPath *)indexPath
|
|
|
|
{
|
|
|
|
TGBridgeMessage *message = _rowModels[indexPath.row];
|
|
|
|
|
|
|
|
TGMessageViewControllerContext *context = nil;
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsChannel] && ![self peerIsChannelGroup])
|
2015-10-01 18:19:52 +02:00
|
|
|
context = [[TGMessageViewControllerContext alloc] initWithMessage:message channel:_chatModel];
|
|
|
|
else
|
|
|
|
context = [[TGMessageViewControllerContext alloc] initWithMessage:message peerId:[self peerId]];
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
context.additionalPeers = _peerModels;
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
[self pushControllerWithClass:[TGMessageViewController class] context:context];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (Class)footerControllerClassForTable:(WKInterfaceTable *)table
|
|
|
|
{
|
2016-02-25 01:03:51 +01:00
|
|
|
if ([self peerIsChannel] && ![self peerIsChannelGroup])
|
2015-10-01 18:19:52 +02:00
|
|
|
return nil;
|
|
|
|
|
|
|
|
return [TGConversationFooterController class];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)table:(WKInterfaceTable *)table updateFooterController:(TGConversationFooterController *)controller
|
|
|
|
{
|
|
|
|
__weak TGNeoConversationController *weakSelf = self;
|
|
|
|
controller.animate = ^(void (^animations)(void))
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf animateWithDuration:0.3 animations:animations];
|
|
|
|
};
|
|
|
|
|
|
|
|
[controller setOptions:_footerOptions animated:!_dontAnimateFooterTransition];
|
|
|
|
_dontAnimateFooterTransition = false;
|
|
|
|
|
|
|
|
controller.stickerPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TGStickersControllerContext *context = [[TGStickersControllerContext alloc] init];
|
|
|
|
context.completionBlock = ^(TGBridgeDocumentMediaAttachment *sticker)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf sendMessageWithStickerAttachment:sticker];
|
|
|
|
};
|
|
|
|
[strongSelf presentControllerWithClass:[TGStickersController class] context:context];
|
|
|
|
};
|
|
|
|
controller.locationPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TGLocationControllerContext *context = [[TGLocationControllerContext alloc] init];
|
|
|
|
context.completionBlock = ^(TGBridgeLocationMediaAttachment *location)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf sendMessageWithLocationAttachment:location];
|
|
|
|
};
|
|
|
|
[strongSelf presentControllerWithClass:[TGLocationController class] context:context];
|
|
|
|
};
|
|
|
|
controller.voicePressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
if (strongSelf->_context.context.micAccessAllowed)
|
2015-10-01 18:19:52 +02:00
|
|
|
{
|
2016-02-25 01:03:51 +01:00
|
|
|
[TGInputController presentAudioControllerForInterfaceController:strongSelf completion:^(int64_t uniqueId, int32_t duration, NSURL *url)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf sendAudioWithUniqueId:uniqueId duration:duration url:url];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[strongSelf presentControllerWithClass:[TGAudioMicAlertController class] context:nil];
|
|
|
|
}
|
2015-10-01 18:19:52 +02:00
|
|
|
};
|
|
|
|
controller.commandsPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (strongSelf->_botReplyMarkup != nil)
|
|
|
|
{
|
|
|
|
TGBridgeBotReplyMarkup *replyMarkup = strongSelf->_botReplyMarkup;
|
|
|
|
TGBotKeyboardControllerContext *context = [[TGBotKeyboardControllerContext alloc] init];
|
|
|
|
context.replyMarkup = replyMarkup;
|
|
|
|
context.completionBlock = ^(NSString *command)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf sendMessageWithText:command replyToMessage:replyMarkup.message];
|
|
|
|
};
|
|
|
|
[strongSelf presentControllerWithClass:[TGBotKeyboardController class] context:context];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TGBotCommandControllerContext *context = [[TGBotCommandControllerContext alloc] init];
|
|
|
|
context.commandListSignal = [strongSelf botCommandListSignal];
|
|
|
|
context.context = strongSelf->_context.context;
|
|
|
|
context.completionBlock = ^(NSString *command)
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf sendMessageWithText:command];
|
|
|
|
};
|
|
|
|
[strongSelf presentControllerWithClass:[TGBotCommandController class] context:context];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
controller.unblockPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf->_updateSettingsDisposable setDisposable:[[[TGBridgePeerSettingsSignals updateBlockStatusWithPeerId:[strongSelf peerId] blocked:false] deliverOn:[SQueue mainQueue]] startWithNext:nil completed:^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_blocked = false;
|
|
|
|
[strongSelf reloadData];
|
|
|
|
}]];
|
|
|
|
};
|
|
|
|
controller.replyPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[TGInputController presentInputControllerForInterfaceController:strongSelf suggestionsForText:[strongSelf _latestIncomingMessage].text completion:^(NSString *text)
|
|
|
|
{
|
|
|
|
[strongSelf sendMessageWithText:text];
|
|
|
|
}];
|
|
|
|
};
|
|
|
|
controller.startPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf sendMessageWithText:@"/start"];
|
|
|
|
};
|
|
|
|
controller.restartPressed = ^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[strongSelf->_updateSettingsDisposable setDisposable:[[[TGBridgePeerSettingsSignals updateBlockStatusWithPeerId:[strongSelf peerId] blocked:false] deliverOn:[SQueue mainQueue]] startWithNext:nil completed:^
|
|
|
|
{
|
|
|
|
__strong TGNeoConversationController *strongSelf = weakSelf;
|
|
|
|
if (strongSelf == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strongSelf->_blocked = false;
|
|
|
|
[strongSelf sendMessageWithText:@"/start"];
|
|
|
|
}]];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
+ (NSMutableArray *)reversedMessagesArray:(NSArray *)array
|
|
|
|
{
|
|
|
|
if (array == nil)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
NSMutableArray *reversedArray = [[NSMutableArray alloc] init];
|
|
|
|
for (id object in array)
|
|
|
|
[reversedArray insertObject:object atIndex:0];
|
|
|
|
|
|
|
|
return reversedArray;
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:03:51 +01:00
|
|
|
+ (NSArray *)timestampedModelsArray:(NSArray *)models
|
|
|
|
{
|
|
|
|
NSMutableArray *newModels = [[NSMutableArray alloc] init];
|
|
|
|
TGChatTimestamp *lastTimestamp = nil;
|
|
|
|
|
|
|
|
for (id model in models)
|
|
|
|
{
|
|
|
|
if ([model isKindOfClass:[TGChatTimestamp class]])
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if ([model isKindOfClass:[TGBridgeMessage class]])
|
|
|
|
{
|
|
|
|
TGBridgeMessage *message = (TGBridgeMessage *)model;
|
|
|
|
|
|
|
|
TGChatTimestamp *timestamp = [TGDateUtils timestampForDateIfNeeded:message.date previousDate:lastTimestamp ? @(lastTimestamp.date) : nil];
|
|
|
|
|
|
|
|
if (timestamp != nil)
|
|
|
|
{
|
|
|
|
lastTimestamp = timestamp;
|
|
|
|
[newModels addObject:timestamp];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[newModels addObject:model];
|
|
|
|
}
|
|
|
|
|
|
|
|
return newModels;
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:19:52 +02:00
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
+ (NSString *)identifier
|
|
|
|
{
|
|
|
|
return TGNeoConversationControllerIdentifier;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|