1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-04 02:17:51 +01:00
Telegram/Watch/Extension/TGNeoConversationController.m
2016-02-25 01:03:51 +01:00

1305 lines
45 KiB
Objective-C

#import "TGNeoConversationController.h"
#import "TGNeoChatsController.h"
#import "TGStringUtils.h"
#import "TGDateUtils.h"
#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"
#import "TGBridgeAudioSignals.h"
#import "TGNeoConversationRowController.h"
#import "TGNeoConversationStaticRowController.h"
#import "TGNeoConversationTimeRowController.h"
#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"
#import "TGAudioMicAlertController.h"
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;
SMetaDisposable *_sentMediaDisposable;
SMetaDisposable *_playAudioDisposable;
TGBridgeMediaAttachment *_pendingAudioAttachment;
TGBridgeChat *_chatModel;
TGBridgeChatMessageListView *_messageListView;
TGBridgeBotInfo *_botInfo;
TGBridgeBotReplyMarkup *_botReplyMarkup;
NSDictionary *_peerModels;
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];
_sentMediaDisposable = [[SMetaDisposable alloc] init];
_playAudioDisposable = [[SMetaDisposable alloc] init];
_pendingSentMessages = [[NSMutableArray alloc] init];
_dontAnimateFooterTransition = true;
self.table.reloadDataReversed = true;
self.table.tableDataSource = self;
[self.table _setInitialHidden:true];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(synchronizationStateUpdated:) name:TGSynchronizationStateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextUpdated:) name:TGContextNotification object:nil];
}
return self;
}
- (void)dealloc
{
[_messagesListDisposable dispose];
[_chatGroupDisposable dispose];
[_sendMessageDisposable dispose];
[_readMessagesDisposable dispose];
[_peerSettingsDisposable dispose];
[_updateSettingsDisposable dispose];
[_botInfoDisposable dispose];
[_botReplyMarkupDisposable dispose];
[_remoteActionDisposable dispose];
[_sentMediaDisposable dispose];
[_playAudioDisposable dispose];
[[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;
[[TGBridgeUserCache instance] storeUsers:[models[TGBridgeUsersDictionaryKey] allValues]];
strongSelf->_peerModels = models[TGBridgeUsersDictionaryKey];
[strongSelf _readMessagesIfNeeded];
[strongSelf performInterfaceUpdate:^(bool animated)
{
__strong TGNeoConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf reloadData];
}];
}]];
if ([self peerIsAnyGroup])
{
[_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];
}];
}]];
}
}
if ([self peerIsAnyGroup] || _hasBots)
{
[_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];
}];
}]];
[_sentMediaDisposable setDisposable:[[TGBridgeAudioSignals sentAudioForConversationId:[self peerId]] startWithNext:^(id next)
{
}]];
[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;
}
if ([self peerIsAnyGroup] || !_hasBots)
_footerOptions |= TGConversationFooterOptionsVoice;
NSMutableArray *pendingSentMessages = [[NSMutableArray alloc] init];
for (TGBridgeMessage *message in _pendingSentMessages)
{
TGBridgeDocumentMediaAttachment *documentAttachment = nil;
TGBridgeLocationMediaAttachment *locationAttachment = nil;
TGBridgeAudioMediaAttachment *audioAttachment = nil;
for (TGBridgeMediaAttachment *attachment in message.media)
{
if ([attachment isKindOfClass:[TGBridgeDocumentMediaAttachment class]])
documentAttachment = (TGBridgeDocumentMediaAttachment *)attachment;
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;
if (fabs(realMessage.date - message.date) > 4.0)
continue;
if ([realMessage.text isEqualToString:message.text])
{
skip = true;
}
else
{
TGBridgeDocumentMediaAttachment *realDocumentAttachment = nil;
TGBridgeLocationMediaAttachment *realLocationAttachment = nil;
TGBridgeAudioMediaAttachment *realAudioAttachment = nil;
for (TGBridgeMediaAttachment *attachment in message.media)
{
if ([attachment isKindOfClass:[TGBridgeDocumentMediaAttachment class]])
realDocumentAttachment = (TGBridgeDocumentMediaAttachment *)attachment;
else if ([attachment isKindOfClass:[TGBridgeLocationMediaAttachment class]])
realLocationAttachment = (TGBridgeLocationMediaAttachment *)attachment;
else if ([attachment isKindOfClass:[TGBridgeAudioMediaAttachment class]])
realAudioAttachment = (TGBridgeAudioMediaAttachment *)attachment;
}
if ([realDocumentAttachment isEqual:documentAttachment] || [realLocationAttachment isEqual:locationAttachment] || [realAudioAttachment isEqual:audioAttachment])
{
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];
chatInfo.title = TGLocalized(@"Bot.DescriptionTitle");
chatInfo.text = _botInfo.botDescription;
[rowModels insertObject:chatInfo atIndex:0];
}
_rowModels = [TGNeoConversationController timestampedModelsArray:rowModels];
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];
bool isGroup = [self peerIsGroup] || [self peerIsChannel];
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
{
//TGBridgeSynchronizationStateValue value = (TGBridgeSynchronizationStateValue)[notification.userInfo[TGSynchronizationStateKey] integerValue];
//if (self.isVisible)
// [self updateTitleWithState:value];
}
- (void)contextUpdated:(NSNotification *)notification
{
TGBridgeContext *context = notification.userInfo[TGContextNotificationKey];
if (context != nil)
_context.context = context;
}
- (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;
TGInterfaceMenuItem *infoItem = [[TGInterfaceMenuItem alloc] initWithItemIcon:WKMenuItemIconInfo title:[self peerIsAnyGroup] ? TGLocalized(@"Watch.Conversation.GroupInfo") : TGLocalized(@"Watch.Conversation.UserInfo") actionBlock:^(TGInterfaceController *controller, TGInterfaceMenuItem *sender)
{
__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;
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];
TGInterfaceMenuItem *muteItem = [[TGInterfaceMenuItem alloc] initWithItemIcon:muted ? WKMenuItemIconSpeaker : WKMenuItemIconMute title:muted ? TGLocalized(@"Watch.UserInfo.Unmute") : muteTitle actionBlock:^(TGInterfaceController *controller, TGInterfaceMenuItem *sender)
{
__strong TGNeoConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGBridgePeerNotificationSettings *settings = [[TGBridgePeerNotificationSettings alloc] init];
settings.muteFor = muted ? 0 : (muteFor == INT_MAX ? INT_MAX : muteFor * 60 * 60);
[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])
{
TGInterfaceMenuItem *blockItem = [[TGInterfaceMenuItem alloc] initWithItemIcon:WKMenuItemIconBlock title:blocked ? TGLocalized(@"Watch.UserInfo.Unblock") : TGLocalized(@"Watch.UserInfo.Block") actionBlock:^(TGInterfaceController *controller, TGInterfaceMenuItem *sender)
{
__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;
}
- (bool)peerIsChannelGroup
{
if (_chatModel != nil)
return _chatModel.isChannelGroup;
else
return false;
}
- (bool)peerIsAnyGroup
{
return [self peerIsGroup] || [self peerIsChannelGroup];
}
#pragma mark - Bots
- (SSignal *)botCommandListSignal
{
if (!_hasBots)
return nil;
if ([self peerIsAnyGroup])
{
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;
if ([self peerIsAnyGroup])
{
[_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;
[_pendingSentMessages addObject:[TGBridgeMessage temporaryNewMessageForAudioWithDuration:duration userId:_context.context.userId localAudioId:uniqueId]];
__weak TGNeoConversationController *weakSelf = self;
[self performInterfaceUpdate:^(bool animated)
{
__strong TGNeoConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf reloadData];
}];
NSDictionary *metadata = @
{
TGBridgeIncomingFileTypeKey: TGBridgeIncomingFileTypeAudio,
TGBridgeIncomingFileRandomIdKey: @(uniqueId),
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];
}
else if ([model isKindOfClass:[TGChatTimestamp class]])
{
return [TGNeoConversationTimeRowController class];
}
return nil;
}
- (NSUInteger)numberOfRowsInTable:(WKInterfaceTable *)table section:(NSUInteger)section
{
return _rowModels.count;
}
- (void)table:(WKInterfaceTable *)table updateRowController:(TGTableRowController *)controller forIndexPath:(TGIndexPath *)indexPath
{
__weak TGNeoConversationController *weakSelf = self;
id model = _rowModels[indexPath.row];
NSUInteger index = [self numberOfRowsInTable:self.table section:0] - indexPath.row - 1;
if ([model isKindOfClass:[TGChatTimestamp class]])
{
TGNeoConversationTimeRowController *timeController = (TGNeoConversationTimeRowController *)controller;
[timeController updateWithTimestamp:model];
return;
}
TGNeoRowController *rowController = (TGNeoRowController *)controller;
rowController.shouldRenderContent = ^bool
{
__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;
__weak TGNeoConversationRowController *weakConversationRow = conversationRow;
conversationRow.animate = ^(void (^animations)(void))
{
__strong TGNeoConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf animateWithDuration:0.25 animations:animations];
};
conversationRow.buttonPressed = ^
{
__strong TGNeoConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
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]];
}
};
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];
}
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;
if ([self peerIsChannel] && ![self peerIsChannelGroup])
context = [[TGMessageViewControllerContext alloc] initWithMessage:message channel:_chatModel];
else
context = [[TGMessageViewControllerContext alloc] initWithMessage:message peerId:[self peerId]];
context.additionalPeers = _peerModels;
[self pushControllerWithClass:[TGMessageViewController class] context:context];
}
- (Class)footerControllerClassForTable:(WKInterfaceTable *)table
{
if ([self peerIsChannel] && ![self peerIsChannelGroup])
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;
if (strongSelf->_context.context.micAccessAllowed)
{
[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];
}
};
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;
}
+ (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;
}
#pragma mark -
+ (NSString *)identifier
{
return TGNeoConversationControllerIdentifier;
}
@end