NCRoomsManager.m 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "NCRoomsManager.h"
  6. #import <Realm/Realm.h>
  7. #import "AppDelegate.h"
  8. #import "CallKitManager.h"
  9. #import "NCChatBlock.h"
  10. #import "NCChatController.h"
  11. #import "NCChatMessage.h"
  12. #import "NCDatabaseManager.h"
  13. #import "NCExternalSignalingController.h"
  14. #import "NCSettingsController.h"
  15. #import "NCUserInterfaceController.h"
  16. #import "NotificationCenterNotifications.h"
  17. #import "NextcloudTalk-Swift.h"
  18. NSString * const NCRoomsManagerDidJoinRoomNotification = @"NCRoomsManagerDidJoinRoomNotification";
  19. NSString * const NCRoomsManagerDidLeaveRoomNotification = @"NCRoomsManagerDidLeaveRoomNotification";
  20. NSString * const NCRoomsManagerDidUpdateRoomsNotification = @"NCRoomsManagerDidUpdateRoomsNotification";
  21. NSString * const NCRoomsManagerDidUpdateRoomNotification = @"NCRoomsManagerDidUpdateRoomNotification";
  22. NSString * const NCRoomsManagerDidStartCallNotification = @"NCRoomsManagerDidStartCallNotification";
  23. NSString * const NCRoomsManagerDidReceiveChatMessagesNotification = @"ChatMessagesReceivedNotification";
  24. static NSInteger kNotJoiningAnymoreStatusCode = 999;
  25. @interface NCRoomsManager () <CallViewControllerDelegate>
  26. @end
  27. @implementation NCRoomController
  28. - (instancetype)init
  29. {
  30. self = [super init];
  31. if (self) {
  32. [[AllocationTracker shared] addAllocation:@"NCRoomController"];
  33. }
  34. return self;
  35. }
  36. - (void)dealloc
  37. {
  38. [[AllocationTracker shared] removeAllocation:@"NCRoomController"];
  39. }
  40. @end
  41. @implementation NCRoomsManager
  42. + (NCRoomsManager *)sharedInstance
  43. {
  44. static dispatch_once_t once;
  45. static NCRoomsManager *sharedInstance;
  46. dispatch_once(&once, ^{
  47. sharedInstance = [[self alloc] init];
  48. });
  49. return sharedInstance;
  50. }
  51. - (id)init
  52. {
  53. self = [super init];
  54. if (self) {
  55. _activeRooms = [[NSMutableDictionary alloc] init];
  56. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinChatWithLocalNotification:) name:NCLocalNotificationJoinChatNotification object:nil];
  57. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinChat:) name:NCPushNotificationJoinChatNotification object:nil];
  58. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinAudioCallAccepted:) name:NCPushNotificationJoinAudioCallAcceptedNotification object:nil];
  59. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinVideoCallAccepted:) name:NCPushNotificationJoinVideoCallAcceptedNotification object:nil];
  60. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedUserForChat:) name:NCSelectedUserForChatNotification object:nil];
  61. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(roomCreated:) name:NCRoomCreatedNotification object:nil];
  62. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptCallForRoom:) name:CallKitManagerDidAnswerCallNotification object:nil];
  63. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startCallForRoom:) name:CallKitManagerDidStartCallNotification object:nil];
  64. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkForCallUpgrades:) name:CallKitManagerDidEndCallNotification object:nil];
  65. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinOrCreateChat:) name:NSNotification.NCChatViewControllerReplyPrivatelyNotification object:nil];
  66. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinChatOfForwardedMessage:) name:NSNotification.NCChatViewControllerForwardNotification object:nil];
  67. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinOrCreateChat:) name:NSNotification.NCChatViewControllerTalkToUserNotification object:nil];
  68. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinOrCreateChatWithURL:) name:NCURLWantsToOpenConversationNotification object:nil];
  69. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinChatHighlightingMessage:) name:NCPresentChatHighlightingMessageNotification object:nil];
  70. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionStateHasChanged:) name:NCConnectionStateHasChangedNotification object:nil];
  71. }
  72. return self;
  73. }
  74. - (void)dealloc
  75. {
  76. [[NSNotificationCenter defaultCenter] removeObserver:self];
  77. }
  78. #pragma mark - Room
  79. - (void)updateRoomsUpdatingUserStatus:(BOOL)updateStatus onlyLastModified:(BOOL)onlyLastModified
  80. {
  81. [self updateRoomsUpdatingUserStatus:updateStatus onlyLastModified:onlyLastModified withCompletionBlock:nil];
  82. }
  83. - (void)updateRoomsAndChatsUpdatingUserStatus:(BOOL)updateStatus onlyLastModified:(BOOL)onlyLastModified withCompletionBlock:(UpdateRoomsAndChatsCompletionBlock)block
  84. {
  85. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  86. if (onlyLastModified && [activeAccount.lastReceivedModifiedSince integerValue] == 0) {
  87. if (block) {
  88. block(nil);
  89. }
  90. return;
  91. }
  92. [self updateRoomsUpdatingUserStatus:updateStatus onlyLastModified:onlyLastModified withCompletionBlock:^(NSArray *roomsWithNewMessages, TalkAccount *account, NSError *error) {
  93. if (error) {
  94. if (block) {
  95. block(error);
  96. }
  97. return;
  98. }
  99. NSLog(@"Finished rooms update with %lu rooms with new messages", [roomsWithNewMessages count]);
  100. dispatch_group_t chatUpdateGroup = dispatch_group_create();
  101. // When in low power mode, we only update the conversation list and don't load new messages for each room
  102. if (![NSProcessInfo processInfo].isLowPowerModeEnabled && [[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityChatKeepNotifications forAccountId:account.accountId]) {
  103. for (NCRoom *room in roomsWithNewMessages) {
  104. dispatch_group_enter(chatUpdateGroup);
  105. NSLog(@"Updating room %@", room.internalId);
  106. NCChatController *chatController;
  107. if (self.chatViewController && self.chatViewController.chatController && [self.chatViewController.room.internalId isEqualToString:room.internalId]) {
  108. // If there's already a chatController for this room, don't create a new one
  109. chatController = self.chatViewController.chatController;
  110. } else {
  111. chatController = [[NCChatController alloc] initForRoom:room];
  112. }
  113. [chatController updateHistoryInBackgroundWithCompletionBlock:^(NSError *error) {
  114. NSLog(@"Finished updating room %@", room.internalId);
  115. dispatch_group_leave(chatUpdateGroup);
  116. }];
  117. }
  118. }
  119. dispatch_group_notify(chatUpdateGroup, dispatch_get_main_queue(), ^{
  120. // Notify backgroundFetch that we're finished
  121. if (block) {
  122. block(nil);
  123. }
  124. });
  125. }];
  126. }
  127. - (void)updateRoomsUpdatingUserStatus:(BOOL)updateStatus onlyLastModified:(BOOL)onlyLastModified withCompletionBlock:(UpdateRoomsCompletionBlock)block
  128. {
  129. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  130. NSInteger modifiedSince = onlyLastModified ? [activeAccount.lastReceivedModifiedSince integerValue] : 0;
  131. [[NCAPIController sharedInstance] getRoomsForAccount:activeAccount updateStatus:updateStatus modifiedSince:modifiedSince completionBlock:^(NSArray * _Nullable rooms, NSError * _Nullable error) {
  132. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  133. NSMutableArray *roomsWithNewMessages = [NSMutableArray new];
  134. if (!error) {
  135. BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"NCUpdateRoomsTransaction" expirationHandler:^(BGTaskHelper *task) {
  136. NSString *logMessage = [NSString stringWithFormat:@"ExpirationHandler called NCUpdateRoomsTransaction, number of rooms %ld", rooms.count];
  137. [NCUtils log:logMessage];
  138. }];
  139. RLMRealm *realm = [RLMRealm defaultRealm];
  140. NSInteger updateTimestamp = [[NSDate date] timeIntervalSince1970];
  141. [realm transactionWithBlock:^{
  142. // Add or update rooms
  143. for (NSDictionary *roomDict in rooms) {
  144. BOOL roomContainsNewMessages = [self updateRoomWithDict:roomDict withAccount:activeAccount withTimestamp:updateTimestamp withRealm:realm];
  145. if (roomContainsNewMessages) {
  146. NCRoom *room = [NCRoom roomWithDictionary:roomDict andAccountId:activeAccount.accountId];
  147. [roomsWithNewMessages addObject:room];
  148. }
  149. }
  150. }];
  151. // Only delete rooms if it was a complete rooms update (not using modifiedSince)
  152. if (!onlyLastModified) {
  153. [realm transactionWithBlock:^{
  154. // Delete old rooms
  155. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@ AND lastUpdate != %ld", activeAccount.accountId, (long)updateTimestamp];
  156. RLMResults *managedRoomsToBeDeleted = [NCRoom objectsWithPredicate:query];
  157. // Delete messages and chat blocks from old rooms
  158. for (NCRoom *managedRoom in managedRoomsToBeDeleted) {
  159. NSPredicate *query2 = [NSPredicate predicateWithFormat:@"accountId = %@ AND token = %@", activeAccount.accountId, managedRoom.token];
  160. [realm deleteObjects:[NCChatMessage objectsWithPredicate:query2]];
  161. [realm deleteObjects:[NCChatBlock objectsWithPredicate:query2]];
  162. if ([managedRoom isFederated]) {
  163. NSPredicate *federatedCapabilities = [NSPredicate predicateWithFormat:@"accountId = %@ AND remoteServer = %@ AND roomToken = %@", activeAccount.accountId, managedRoom.remoteServer, managedRoom.token];
  164. [realm deleteObjects:[FederatedCapabilities objectsWithPredicate:federatedCapabilities]];
  165. }
  166. }
  167. [realm deleteObjects:managedRoomsToBeDeleted];
  168. }];
  169. }
  170. [bgTask stopBackgroundTask];
  171. } else {
  172. [userInfo setObject:error forKey:@"error"];
  173. [NCUtils log:[NSString stringWithFormat:@"Could not update rooms. Error: %@", error.description]];
  174. }
  175. [[NSNotificationCenter defaultCenter] postNotificationName:NCRoomsManagerDidUpdateRoomsNotification
  176. object:self
  177. userInfo:userInfo];
  178. if (block) {
  179. block(roomsWithNewMessages, activeAccount, error);
  180. }
  181. }];
  182. }
  183. - (void)updateRoom:(NSString *)token withCompletionBlock:(GetRoomCompletionBlock)block
  184. {
  185. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  186. [[NCAPIController sharedInstance] getRoomForAccount:activeAccount withToken:token completionBlock:^(NSDictionary *roomDict, NSError *error) {
  187. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  188. if (!error) {
  189. RLMRealm *realm = [RLMRealm defaultRealm];
  190. [realm transactionWithBlock:^{
  191. [self updateRoomWithDict:roomDict withAccount:activeAccount withTimestamp:[[NSDate date] timeIntervalSince1970] withRealm:realm];
  192. NSLog(@"Room updated");
  193. }];
  194. NCRoom *updatedRoom = [[NCDatabaseManager sharedInstance] roomWithToken:token forAccountId:activeAccount.accountId];
  195. if (updatedRoom) {
  196. // It seems to be somehow possible to have the updatedRoom be nil as seen in AppStore crashes
  197. [userInfo setObject:updatedRoom forKey:@"room"];
  198. }
  199. } else {
  200. [userInfo setObject:error forKey:@"error"];
  201. NSLog(@"Could not update rooms. Error: %@", error.description);
  202. }
  203. [[NSNotificationCenter defaultCenter] postNotificationName:NCRoomsManagerDidUpdateRoomNotification
  204. object:self
  205. userInfo:userInfo];
  206. if (block) {
  207. block(roomDict, error);
  208. }
  209. }];
  210. }
  211. - (BOOL)updateRoomWithDict:(NSDictionary *)roomDict withAccount:(TalkAccount *)activeAccount withTimestamp:(NSInteger)timestamp withRealm:(RLMRealm *)realm
  212. {
  213. BOOL roomContainsNewMessages = NO;
  214. NCRoom *room = [NCRoom roomWithDictionary:roomDict andAccountId:activeAccount.accountId];
  215. room.lastUpdate = timestamp;
  216. NCChatMessage *lastMessage = nil;
  217. NSDictionary *messageDict = [roomDict objectForKey:@"lastMessage"];
  218. if (!room.isFederated) {
  219. // TODO: Move handling to NCRoom roomWithDictionary?
  220. lastMessage = [NCChatMessage messageWithDictionary:messageDict andAccountId:activeAccount.accountId];
  221. room.lastMessageId = lastMessage.internalId;
  222. }
  223. NCRoom *managedRoom = [NCRoom objectsWhere:@"internalId = %@", room.internalId].firstObject;
  224. if (managedRoom) {
  225. if (room.lastActivity > managedRoom.lastActivity) {
  226. roomContainsNewMessages = YES;
  227. }
  228. [NCRoom updateRoom:managedRoom withRoom:room];
  229. } else if (room) {
  230. [realm addObject:room];
  231. }
  232. if (lastMessage) {
  233. NCChatMessage *managedLastMessage = [NCChatMessage objectsWhere:@"internalId = %@", lastMessage.internalId].firstObject;
  234. if (managedLastMessage) {
  235. [NCChatMessage updateChatMessage:managedLastMessage withChatMessage:lastMessage isRoomLastMessage:YES];
  236. } else {
  237. NCChatController *chatController = [[NCChatController alloc] initForRoom:room];
  238. [chatController storeMessages:@[messageDict] withRealm:realm];
  239. }
  240. }
  241. return roomContainsNewMessages;
  242. }
  243. - (void)updatePendingMessage:(NSString *)message forRoom:(NCRoom *)room
  244. {
  245. BGTaskHelper *bgTask = [BGTaskHelper startBackgroundTaskWithName:@"updatePendingMessage" expirationHandler:nil];
  246. RLMRealm *realm = [RLMRealm defaultRealm];
  247. [realm transactionWithBlock:^{
  248. NCRoom *managedRoom = [NCRoom objectsWhere:@"internalId = %@", room.internalId].firstObject;
  249. if (managedRoom) {
  250. managedRoom.pendingMessage = message;
  251. }
  252. }];
  253. [bgTask stopBackgroundTask];
  254. }
  255. - (void)updateLastReadMessage:(NSInteger)lastReadMessage forRoom:(NCRoom *)room
  256. {
  257. RLMRealm *realm = [RLMRealm defaultRealm];
  258. [realm transactionWithBlock:^{
  259. NCRoom *managedRoom = [NCRoom objectsWhere:@"internalId = %@", room.internalId].firstObject;
  260. if (managedRoom) {
  261. managedRoom.lastReadMessage = lastReadMessage;
  262. }
  263. }];
  264. }
  265. - (void)updateLastMessage:(NCChatMessage *)message withNoUnreadMessages:(BOOL)noUnreadMessages forRoom:(NCRoom *)room
  266. {
  267. RLMRealm *realm = [RLMRealm defaultRealm];
  268. [realm transactionWithBlock:^{
  269. NCRoom *managedRoom = [NCRoom objectsWhere:@"internalId = %@", room.internalId].firstObject;
  270. if (managedRoom) {
  271. managedRoom.lastMessageId = message.internalId;
  272. managedRoom.lastActivity = message.timestamp;
  273. if (noUnreadMessages) {
  274. managedRoom.unreadMention = NO;
  275. managedRoom.unreadMentionDirect = NO;
  276. managedRoom.unreadMessages = 0;
  277. }
  278. }
  279. }];
  280. }
  281. - (void)updateLastCommonReadMessage:(NSInteger)messageId forRoom:(NCRoom *)room
  282. {
  283. RLMRealm *realm = [RLMRealm defaultRealm];
  284. [realm transactionWithBlock:^{
  285. NCRoom *managedRoom = [NCRoom objectsWhere:@"internalId = %@", room.internalId].firstObject;
  286. if (managedRoom && messageId > managedRoom.lastCommonReadMessage) {
  287. managedRoom.lastCommonReadMessage = messageId;
  288. }
  289. }];
  290. }
  291. #pragma mark - Chat
  292. - (void)startChatInRoom:(NCRoom *)room
  293. {
  294. if (_callViewController) {
  295. NSLog(@"Not starting chat due to in a call.");
  296. return;
  297. }
  298. NCRoomController *roomController = [_activeRooms objectForKey:room.token];
  299. if (!roomController) {
  300. // Workaround until external signaling supports multi-room
  301. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  302. NCExternalSignalingController *extSignalingController = [[NCSettingsController sharedInstance] externalSignalingControllerForAccountId:activeAccount.accountId];
  303. if (extSignalingController) {
  304. NSString *currentRoom = extSignalingController.currentRoom;
  305. if (![currentRoom isEqualToString:room.token]) {
  306. // Since we are going to join another conversation, we don't need to leaveRoom() in extSignalingController.
  307. // That's why we set currentRoom = nil, so when leaveRoom() is called in extSignalingController the currentRoom
  308. // is no longer the room we want to leave (so no message is sent to the external signaling server).
  309. extSignalingController.currentRoom = nil;
  310. }
  311. }
  312. }
  313. if (!_chatViewController || ![_chatViewController.room.token isEqualToString:room.token]) {
  314. // Leave the previous chat
  315. //[[NCRoomsManager sharedInstance].chatViewController leaveChat];
  316. NSLog(@"Creating new chat view controller.");
  317. _chatViewController = [[ChatViewController alloc] initFor:room];
  318. if (_highlightMessageDict && [[_highlightMessageDict objectForKey:@"token"] isEqualToString:room.token]) {
  319. _chatViewController.highlightMessageId = [[_highlightMessageDict objectForKey:@"messageId"] integerValue];
  320. _highlightMessageDict = nil;
  321. }
  322. [[NCUserInterfaceController sharedInstance] presentChatViewController:_chatViewController];
  323. } else {
  324. NSLog(@"Not creating new chat room: chatViewController for room %@ does already exist.", room.token);
  325. // Still make sure the current room is highlighted
  326. [[NCUserInterfaceController sharedInstance].roomsTableViewController setSelectedRoomToken:_chatViewController.room.token];
  327. }
  328. }
  329. - (void)startChatWithRoomToken:(NSString *)token
  330. {
  331. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  332. NCRoom *room = [[NCDatabaseManager sharedInstance] roomWithToken:token forAccountId:activeAccount.accountId];
  333. if (room) {
  334. [self startChatInRoom:room];
  335. } else {
  336. //TODO: Show spinner?
  337. [[NCAPIController sharedInstance] getRoomForAccount:activeAccount withToken:token completionBlock:^(NSDictionary *roomDict, NSError *error) {
  338. if (!error) {
  339. NCRoom *room = [NCRoom roomWithDictionary:roomDict andAccountId:activeAccount.accountId];
  340. [self startChatInRoom:room];
  341. }
  342. }];
  343. }
  344. }
  345. - (void)leaveChatInRoom:(NSString *)token;
  346. {
  347. NCRoomController *roomController = [_activeRooms objectForKey:token];
  348. if (roomController) {
  349. roomController.inChat = NO;
  350. }
  351. [self leaveRoom:token];
  352. }
  353. #pragma mark - Call
  354. - (void)startCall:(BOOL)video inRoom:(NCRoom *)room withVideoEnabled:(BOOL)enabled asInitiator:(BOOL)initiator silently:(BOOL)silently recordingConsent:(BOOL)recordingConsent andVoiceChatMode:(BOOL)voiceChatMode
  355. {
  356. if (!_callViewController) {
  357. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  358. _callViewController = [[CallViewController alloc] initCallInRoom:room asUser:activeAccount.userDisplayName audioOnly:!video];
  359. _callViewController.videoDisabledAtStart = !enabled;
  360. _callViewController.voiceChatModeAtStart = voiceChatMode;
  361. _callViewController.initiator = initiator;
  362. _callViewController.silentCall = silently;
  363. _callViewController.recordingConsent = recordingConsent;
  364. [_callViewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
  365. _callViewController.delegate = self;
  366. NSString *chatViewControllerRoomToken = _chatViewController.room.token;
  367. NSString *joiningRoomToken = room.token;
  368. // Workaround until external signaling supports multi-room
  369. NCExternalSignalingController *extSignalingController = [[NCSettingsController sharedInstance] externalSignalingControllerForAccountId:activeAccount.accountId];
  370. if (extSignalingController) {
  371. NSString *extSignalingRoomToken = extSignalingController.currentRoom;
  372. if (![extSignalingRoomToken isEqualToString:joiningRoomToken]) {
  373. // Since we are going to join another conversation, we don't need to leaveRoom() in extSignalingController.
  374. // That's why we set currentRoom = nil, so when leaveRoom() is called in extSignalingController the currentRoom
  375. // is no longer the room we want to leave (so no message is sent to the external signaling server).
  376. extSignalingController.currentRoom = nil;
  377. }
  378. }
  379. // Make sure the external signaling contoller is connected.
  380. // Could be that the call has been received while the app was inactive or in the background,
  381. // so the external signaling controller might be disconnected at this point.
  382. if ([extSignalingController disconnected]) {
  383. [extSignalingController forceConnect];
  384. }
  385. if (_chatViewController) {
  386. if ([chatViewControllerRoomToken isEqualToString:joiningRoomToken]) {
  387. // We're in the chat of the room we want to start a call, so stop chat for now
  388. [_chatViewController stopChat];
  389. } else {
  390. // We're in a different chat, so make sure we leave the chat and go back to the conversation list
  391. [_chatViewController leaveChat];
  392. [[NCUserInterfaceController sharedInstance] presentConversationsList];
  393. }
  394. }
  395. [[NCUserInterfaceController sharedInstance] presentCallViewController:_callViewController withCompletionBlock:^{
  396. [self joinRoom:room.token forCall:YES];
  397. }];
  398. } else {
  399. NSLog(@"Not starting call due to in another call.");
  400. }
  401. }
  402. - (void)joinCallWithCallToken:(NSString *)token withVideo:(BOOL)video asInitiator:(BOOL)initiator recordingConsent:(BOOL)recordingConsent
  403. {
  404. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  405. [[NCAPIController sharedInstance] getRoomForAccount:activeAccount withToken:token completionBlock:^(NSDictionary *roomDict, NSError *error) {
  406. if (!error) {
  407. NCRoom *room = [NCRoom roomWithDictionary:roomDict andAccountId:activeAccount.accountId];
  408. [[CallKitManager sharedInstance] startCall:room.token withVideoEnabled:video andDisplayName:room.displayName asInitiator:initiator silently:YES recordingConsent:recordingConsent withAccountId:activeAccount.accountId];
  409. }
  410. }];
  411. }
  412. - (void)startCallWithCallToken:(NSString *)token withVideo:(BOOL)video enabledAtStart:(BOOL)enabled asInitiator:(BOOL)initiator silently:(BOOL)silently recordingConsent:(BOOL)recordingConsent andVoiceChatMode:(BOOL)voiceChatMode
  413. {
  414. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  415. [[NCAPIController sharedInstance] getRoomForAccount:activeAccount withToken:token completionBlock:^(NSDictionary *roomDict, NSError *error) {
  416. if (!error) {
  417. NCRoom *room = [NCRoom roomWithDictionary:roomDict andAccountId:activeAccount.accountId];
  418. [self startCall:video inRoom:room withVideoEnabled:enabled asInitiator:initiator silently:silently recordingConsent:recordingConsent andVoiceChatMode:voiceChatMode];
  419. }
  420. }];
  421. }
  422. - (void)checkForPendingToStartCalls
  423. {
  424. if (_pendingToStartCallToken) {
  425. // Pending calls can only happen when answering a new call. That's why we start with video disabled at start and in voice chat mode.
  426. // We also can start call silently because we are joining an already started call so no need to notify.
  427. [self startCallWithCallToken:_pendingToStartCallToken withVideo:_pendingToStartCallHasVideo enabledAtStart:NO asInitiator:NO silently:YES recordingConsent:NO andVoiceChatMode:YES];
  428. _pendingToStartCallToken = nil;
  429. }
  430. }
  431. - (BOOL)areThereActiveCalls
  432. {
  433. for (NCRoomController *roomController in [_activeRooms allValues]) {
  434. if (roomController.inCall) {
  435. return YES;
  436. }
  437. }
  438. return NO;
  439. }
  440. - (void)upgradeCallToVideoCall:(NCRoom *)room
  441. {
  442. NCRoomController *roomController = [_activeRooms objectForKey:room.token];
  443. if (roomController) {
  444. roomController.inCall = NO;
  445. }
  446. _upgradeCallToken = room.token;
  447. [[CallKitManager sharedInstance] endCall:room.token withStatusCode:0];
  448. }
  449. - (void)leaveCallInRoom:(NSString *)token
  450. {
  451. NCRoomController *roomController = [_activeRooms objectForKey:token];
  452. if (roomController) {
  453. roomController.inCall = NO;
  454. }
  455. [self leaveRoom:token];
  456. }
  457. - (void)callDidEndInRoomWithToken:(NSString *)token
  458. {
  459. [self leaveCallInRoom:token];
  460. [[CallKitManager sharedInstance] endCall:token withStatusCode:0];
  461. if ([_chatViewController.room.token isEqualToString:token]) {
  462. [_chatViewController resumeChat];
  463. }
  464. }
  465. #pragma mark - Switch to
  466. - (void)prepareSwitchToAnotherRoomFromRoom:(NSString *)token withCompletionBlock:(ExitRoomCompletionBlock)block
  467. {
  468. if ([_chatViewController.room.token isEqualToString:token]) {
  469. [_chatViewController leaveChat];
  470. [[NCUserInterfaceController sharedInstance] popToConversationsList];
  471. }
  472. // Remove room controller and exit room
  473. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  474. NCRoomController *roomController = [_activeRooms objectForKey:token];
  475. if (roomController) {
  476. [_activeRooms removeObjectForKey:token];
  477. [[NCAPIController sharedInstance] exitRoom:token forAccount:activeAccount withCompletionBlock:block];
  478. } else {
  479. NSLog(@"Couldn't find a room controller from the room we are switching from");
  480. block(nil);
  481. }
  482. }
  483. #pragma mark - CallViewControllerDelegate
  484. - (void)callViewControllerWantsToBeDismissed:(CallViewController *)viewController
  485. {
  486. if (_callViewController == viewController && ![viewController isBeingDismissed]) {
  487. [viewController dismissViewControllerAnimated:YES completion:nil];
  488. }
  489. }
  490. - (void)callViewControllerWantsVideoCallUpgrade:(CallViewController *)viewController
  491. {
  492. NCRoom *room = _callViewController.room;
  493. if (_callViewController == viewController) {
  494. _callViewController = nil;
  495. [self upgradeCallToVideoCall:room];
  496. }
  497. }
  498. - (void)callViewController:(CallViewController *)viewController wantsToSwitchCallFromCall:(NSString *)from toRoom:(NSString *)to
  499. {
  500. if (_callViewController == viewController) {
  501. [[CallKitManager sharedInstance] switchCallFrom:from toCall:to];
  502. }
  503. }
  504. - (void)callViewControllerDidFinish:(CallViewController *)viewController
  505. {
  506. if (_callViewController == viewController) {
  507. NSString *token = [_callViewController.room.token copy];
  508. _callViewController = nil;
  509. [self callDidEndInRoomWithToken:token];
  510. // Keep connection alive temporarily when a call was finished while the app in the background
  511. if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) {
  512. AppDelegate *appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  513. [appDelegate keepExternalSignalingConnectionAliveTemporarily];
  514. }
  515. }
  516. }
  517. #pragma mark - Notifications
  518. - (void)checkForCallUpgrades:(NSNotification *)notification
  519. {
  520. if (_upgradeCallToken) {
  521. NSString *token = [_upgradeCallToken copy];
  522. _upgradeCallToken = nil;
  523. // Add some delay so CallKit doesn't fail requesting new call
  524. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
  525. [self joinCallWithCallToken:token withVideo:YES asInitiator:NO recordingConsent:YES];
  526. });
  527. }
  528. }
  529. - (void)checkForAccountChange:(NSString *)accountId
  530. {
  531. // Change account if notification is from another account
  532. if (accountId && ![[[NCDatabaseManager sharedInstance] activeAccount].accountId isEqualToString:accountId]) {
  533. // Leave chat before changing accounts
  534. if ([[NCRoomsManager sharedInstance] chatViewController]) {
  535. [[[NCRoomsManager sharedInstance] chatViewController] leaveChat];
  536. }
  537. // Set notification account active
  538. [[NCSettingsController sharedInstance] setActiveAccountWithAccountId:accountId];
  539. }
  540. }
  541. - (void)acceptCallForRoom:(NSNotification *)notification
  542. {
  543. NSString *roomToken = [notification.userInfo objectForKey:@"roomToken"];
  544. BOOL waitForCallEnd = [[notification.userInfo objectForKey:@"waitForCallEnd"] boolValue];
  545. BOOL hasVideo = [[notification.userInfo objectForKey:@"hasVideo"] boolValue];
  546. BOOL activeCalls = [self areThereActiveCalls];
  547. if (!waitForCallEnd || (!activeCalls && !_leaveRoomTask)) {
  548. // Calls that have been answered start with video disabled by default, in voice chat mode and silently (without notification).
  549. [self startCallWithCallToken:roomToken withVideo:hasVideo enabledAtStart:NO asInitiator:NO silently:YES recordingConsent:NO andVoiceChatMode:YES];
  550. } else {
  551. _pendingToStartCallToken = roomToken;
  552. _pendingToStartCallHasVideo = hasVideo;
  553. }
  554. }
  555. - (void)startCallForRoom:(NSNotification *)notification
  556. {
  557. NSString *roomToken = [notification.userInfo objectForKey:@"roomToken"];
  558. BOOL isVideoEnabled = [[notification.userInfo objectForKey:@"isVideoEnabled"] boolValue];
  559. BOOL initiator = [[notification.userInfo objectForKey:@"initiator"] boolValue];
  560. BOOL silentCall = [[notification.userInfo objectForKey:@"silentCall"] boolValue];
  561. BOOL recordingConsent = [[notification.userInfo objectForKey:@"recordingConsent"] boolValue];
  562. [self startCallWithCallToken:roomToken withVideo:isVideoEnabled enabledAtStart:YES asInitiator:initiator silently:silentCall recordingConsent:recordingConsent andVoiceChatMode:NO];
  563. }
  564. - (void)joinAudioCallAccepted:(NSNotification *)notification
  565. {
  566. NCPushNotification *pushNotification = [notification.userInfo objectForKey:@"pushNotification"];
  567. [self checkForAccountChange:pushNotification.accountId];
  568. [self joinCallWithCallToken:pushNotification.roomToken withVideo:NO asInitiator:NO recordingConsent:NO];
  569. }
  570. - (void)joinVideoCallAccepted:(NSNotification *)notification
  571. {
  572. NCPushNotification *pushNotification = [notification.userInfo objectForKey:@"pushNotification"];
  573. [self checkForAccountChange:pushNotification.accountId];
  574. [self joinCallWithCallToken:pushNotification.roomToken withVideo:YES asInitiator:NO recordingConsent:NO];
  575. }
  576. - (void)joinChat:(NSNotification *)notification
  577. {
  578. NCPushNotification *pushNotification = [notification.userInfo objectForKey:@"pushNotification"];
  579. [self checkForAccountChange:pushNotification.accountId];
  580. [self startChatWithRoomToken:pushNotification.roomToken];
  581. }
  582. - (void)joinOrCreateChatWithUser:(NSString *)userId usingAccountId:(NSString *)accountId
  583. {
  584. NSArray *accountRooms = [[NCRoomsManager sharedInstance] roomsForAccountId:accountId withRealm:nil];
  585. for (NCRoom *room in accountRooms) {
  586. NSArray *participantsInRoom = [room.participants valueForKey:@"self"];
  587. if (room.type == kNCRoomTypeOneToOne && [participantsInRoom containsObject:userId]) {
  588. // Room already exists -> join the room
  589. [self startChatWithRoomToken:room.token];
  590. return;
  591. }
  592. }
  593. // Did not find a one-to-one room for this user -> create a new one
  594. [[NCAPIController sharedInstance] createRoomForAccount:[[NCDatabaseManager sharedInstance] activeAccount] withInvite:userId
  595. ofType:kNCRoomTypeOneToOne
  596. andName:nil
  597. completionBlock:^(NCRoom *room, NSError *error) {
  598. if (!error) {
  599. [self startChatWithRoomToken:room.token];
  600. NSLog(@"Room %@ with %@ created", room.token, userId);
  601. } else {
  602. NSLog(@"Failed creating a room with %@", userId);
  603. }
  604. }];
  605. }
  606. - (void)joinOrCreateChat:(NSNotification *)notification
  607. {
  608. NSString *actorId = [notification.userInfo objectForKey:@"actorId"];
  609. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  610. [self joinOrCreateChatWithUser:actorId usingAccountId:activeAccount.accountId];
  611. }
  612. - (void)joinOrCreateChatWithURL:(NSNotification *)notification
  613. {
  614. NSString *accountId = [notification.userInfo objectForKey:@"accountId"];
  615. NSString *withUser = [notification.userInfo objectForKey:@"withUser"];
  616. NSString *roomToken = [notification.userInfo objectForKey:@"withRoomToken"];
  617. [self checkForAccountChange:accountId];
  618. if (withUser) {
  619. [self joinOrCreateChatWithUser:withUser usingAccountId:accountId];
  620. } else if (roomToken) {
  621. [self startChatWithRoomToken:roomToken];
  622. } // else: no chat specified, only change account
  623. }
  624. - (void)joinChatOfForwardedMessage:(NSNotification *)notification
  625. {
  626. NSString *accountId = [notification.userInfo objectForKey:@"accountId"];
  627. NSString *token = [notification.userInfo objectForKey:@"token"];
  628. [self checkForAccountChange:accountId];
  629. [self startChatWithRoomToken:token];
  630. }
  631. - (void)joinChatWithLocalNotification:(NSNotification *)notification
  632. {
  633. NSString *roomToken = [notification.userInfo objectForKey:@"roomToken"];
  634. if (roomToken) {
  635. NSString *accountId = [notification.userInfo objectForKey:@"accountId"];
  636. [self checkForAccountChange:accountId];
  637. [self startChatWithRoomToken:roomToken];
  638. // In case this notification occurred because of a failed chat-sending event, make sure the text is not lost
  639. // Note: This will override any stored pending message
  640. NSString *responseUserText = [notification.userInfo objectForKey:@"responseUserText"];
  641. if (_chatViewController && responseUserText) {
  642. [_chatViewController setChatMessage:responseUserText];
  643. }
  644. }
  645. }
  646. - (void)joinChatHighlightingMessage:(NSNotification *)notification
  647. {
  648. _highlightMessageDict = notification.userInfo;
  649. NSString *token = [notification.userInfo objectForKey:@"token"];
  650. [self startChatWithRoomToken:token];
  651. }
  652. - (void)selectedUserForChat:(NSNotification *)notification
  653. {
  654. NSString *roomToken = [notification.userInfo objectForKey:@"token"];
  655. [self startChatWithRoomToken:roomToken];
  656. }
  657. - (void)roomCreated:(NSNotification *)notification
  658. {
  659. NSString *roomToken = [notification.userInfo objectForKey:@"token"];
  660. [self startChatWithRoomToken:roomToken];
  661. }
  662. - (void)connectionStateHasChanged:(NSNotification *)notification
  663. {
  664. ConnectionState connectionState = [[notification.userInfo objectForKey:@"connectionState"] intValue];
  665. // Try to send offline message when the connection state changes to connected again
  666. if (connectionState == kConnectionStateConnected) {
  667. [self resendOfflineMessagesWithCompletionBlock:nil];
  668. }
  669. }
  670. @end