NCUserInterfaceController.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "NCUserInterfaceController.h"
  6. #import "AFNetworking.h"
  7. #import "JDStatusBarNotification.h"
  8. #import "UIView+Toast.h"
  9. #import "AuthenticationViewController.h"
  10. #import "LoginViewController.h"
  11. #import "NCAppBranding.h"
  12. #import "NCConnectionController.h"
  13. #import "NCDatabaseManager.h"
  14. #import "NCRoomsManager.h"
  15. #import "NCSettingsController.h"
  16. #import "NotificationCenterNotifications.h"
  17. #import "NextcloudTalk-Swift.h"
  18. @interface NCUserInterfaceController () <LoginViewControllerDelegate, AuthenticationViewControllerDelegate>
  19. {
  20. LoginViewController *_loginViewController;
  21. AuthenticationViewController *_authViewController;
  22. NCPushNotification *_pendingPushNotification;
  23. NSMutableDictionary *_pendingCallKitCall;
  24. NSDictionary *_pendingLocalNotification;
  25. NSURLComponents *_pendingURL;
  26. BOOL _waitingForServerCapabilities;
  27. }
  28. @end
  29. @implementation NCUserInterfaceController
  30. + (NCUserInterfaceController *)sharedInstance
  31. {
  32. static dispatch_once_t once;
  33. static NCUserInterfaceController *sharedInstance;
  34. dispatch_once(&once, ^{
  35. sharedInstance = [[self alloc] init];
  36. });
  37. return sharedInstance;
  38. }
  39. - (id)init
  40. {
  41. self = [super init];
  42. if (self) {
  43. [self configureToasts];
  44. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appStateHasChanged:) name:NCAppStateHasChangedNotification object:nil];
  45. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionStateHasChanged:) name:NCConnectionStateHasChangedNotification object:nil];
  46. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(presentTalkNotInstalledWarningAlert) name:NCTalkNotInstalledNotification object:nil];
  47. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(presentTalkOutdatedWarningAlert) name:NCOutdatedTalkVersionNotification object:nil];
  48. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(presentServerMaintenanceModeWarning:) name:NCServerMaintenanceModeNotification object:nil];
  49. }
  50. return self;
  51. }
  52. - (void)dealloc
  53. {
  54. [[NSNotificationCenter defaultCenter] removeObserver:self];
  55. }
  56. - (void)configureToasts
  57. {
  58. CSToastStyle *style = [[CSToastStyle alloc] initWithDefaultStyle];
  59. style.messageFont = [UIFont systemFontOfSize:15.0];
  60. style.backgroundColor = [UIColor colorWithWhite:0.3 alpha:1];
  61. style.cornerRadius = 5.0;
  62. [CSToastManager setSharedStyle:style];
  63. }
  64. - (void)presentLoginViewController
  65. {
  66. [self presentLoginViewControllerForServerURL:nil withUser:nil];
  67. }
  68. - (void)presentLoginViewControllerForServerURL:(NSString *)serverURL withUser:(NSString *)user
  69. {
  70. if (forceDomain && domain) {
  71. _authViewController = [[AuthenticationViewController alloc] initWithServerUrl:domain];
  72. _authViewController.delegate = self;
  73. _authViewController.modalPresentationStyle = ([[NCDatabaseManager sharedInstance] numberOfAccounts] == 0) ? UIModalPresentationFullScreen : UIModalPresentationAutomatic;
  74. [_mainViewController presentViewController:_authViewController animated:YES completion:nil];
  75. } else {
  76. // Don't open a login if we're in a call
  77. if ([[NCRoomsManager sharedInstance] callViewController]) {
  78. return;
  79. }
  80. // Leave chat if we're currently in one
  81. if ([[NCRoomsManager sharedInstance] chatViewController]) {
  82. [self presentConversationsList];
  83. }
  84. if (!_loginViewController || [_mainViewController presentedViewController] != _loginViewController) {
  85. _loginViewController = [[LoginViewController alloc] init];
  86. _loginViewController.delegate = self;
  87. _loginViewController.modalPresentationStyle = ([[NCDatabaseManager sharedInstance] numberOfAccounts] == 0) ? UIModalPresentationFullScreen : UIModalPresentationAutomatic;
  88. [_mainViewController presentViewController:_loginViewController animated:YES completion:nil];
  89. }
  90. if (serverURL) {
  91. [_loginViewController startLoginProcessWithServerURL:serverURL withUser:user];
  92. }
  93. }
  94. }
  95. - (void)presentLoggedOutInvalidCredentialsAlert
  96. {
  97. UIAlertController * alert = [UIAlertController
  98. alertControllerWithTitle:NSLocalizedString(@"Logged out", nil)
  99. message:NSLocalizedString(@"Credentials for this account were no longer valid", nil)
  100. preferredStyle:UIAlertControllerStyleAlert];
  101. UIAlertAction* okButton = [UIAlertAction
  102. actionWithTitle:NSLocalizedString(@"OK", nil)
  103. style:UIAlertActionStyleDefault
  104. handler:nil];
  105. [alert addAction:okButton];
  106. [_mainViewController presentViewController:alert animated:YES completion:nil];
  107. }
  108. - (void)presentOfflineWarningAlert
  109. {
  110. UIAlertController * alert = [UIAlertController
  111. alertControllerWithTitle:NSLocalizedString(@"Disconnected", nil)
  112. message:NSLocalizedString(@"It seems that there is no internet connection.", nil)
  113. preferredStyle:UIAlertControllerStyleAlert];
  114. UIAlertAction* okButton = [UIAlertAction
  115. actionWithTitle:NSLocalizedString(@"OK", nil)
  116. style:UIAlertActionStyleDefault
  117. handler:nil];
  118. [alert addAction:okButton];
  119. [_mainViewController presentViewController:alert animated:YES completion:nil];
  120. }
  121. - (void)presentTalkNotInstalledWarningAlert
  122. {
  123. UIAlertController * alert = [UIAlertController
  124. alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedString(@"%@ not installed", @"{app name} is not installed"), talkAppName]
  125. message:[NSString stringWithFormat:NSLocalizedString(@"It seems that %@ is not installed in your server.", @"It seems that {app name} is not installed in your server."), talkAppName]
  126. preferredStyle:UIAlertControllerStyleAlert];
  127. UIAlertAction* okButton = [UIAlertAction
  128. actionWithTitle:NSLocalizedString(@"OK", nil)
  129. style:UIAlertActionStyleDefault
  130. handler:^(UIAlertAction * _Nonnull action) {
  131. [self logOutCurrentUser];
  132. }];
  133. [alert addAction:okButton];
  134. [_mainViewController presentViewController:alert animated:YES completion:nil];
  135. }
  136. - (void)presentTalkOutdatedWarningAlert
  137. {
  138. UIAlertController * alert = [UIAlertController
  139. alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedString(@"%@ version not supported", @"{app name} version not supported"), talkAppName]
  140. message:[NSString stringWithFormat:NSLocalizedString(@"Please update your server with the latest %@ version available.", @"Please update your server with the latest {app name} version available."), talkAppName]
  141. preferredStyle:UIAlertControllerStyleAlert];
  142. UIAlertAction* okButton = [UIAlertAction
  143. actionWithTitle:NSLocalizedString(@"OK", nil)
  144. style:UIAlertActionStyleDefault
  145. handler:^(UIAlertAction * _Nonnull action) {
  146. [self logOutCurrentUser];
  147. }];
  148. [alert addAction:okButton];
  149. [_mainViewController presentViewController:alert animated:YES completion:nil];
  150. }
  151. - (void)presentAccountNotConfiguredAlertForUser:(NSString *)user inServer:(NSString *)server
  152. {
  153. UIAlertController * alert = [UIAlertController
  154. alertControllerWithTitle:NSLocalizedString(@"Account not configured", nil)
  155. message:[NSString stringWithFormat:NSLocalizedString(@"There is no account for user %@ in server %@ configured in this app.", nil), user, server]
  156. preferredStyle:UIAlertControllerStyleAlert];
  157. UIAlertAction* okButton = [UIAlertAction
  158. actionWithTitle:NSLocalizedString(@"OK", nil)
  159. style:UIAlertActionStyleDefault
  160. handler:nil];
  161. [alert addAction:okButton];
  162. [_mainViewController presentViewController:alert animated:YES completion:nil];
  163. }
  164. - (void)presentServerMaintenanceModeWarning:(NSNotification *)notification
  165. {
  166. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  167. NSString *accountId = [notification.userInfo objectForKey:@"accountId"];
  168. if (accountId && [activeAccount.accountId isEqualToString:accountId]) {
  169. [[JDStatusBarNotificationPresenter sharedPresenter] presentWithText:NSLocalizedString(@"Server is currently in maintenance mode", nil) dismissAfterDelay:4.0f includedStyle:JDStatusBarNotificationIncludedStyleError];
  170. }
  171. }
  172. - (void)logOutAccountWithAccountId:(NSString *)accountId
  173. {
  174. [[NCSettingsController sharedInstance] logoutAccountWithAccountId:accountId withCompletionBlock:^(NSError *error) {
  175. [[NCUserInterfaceController sharedInstance] presentConversationsList];
  176. [[NCConnectionController sharedInstance] checkAppState];
  177. }];
  178. }
  179. - (void)logOutCurrentUser
  180. {
  181. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  182. [self logOutAccountWithAccountId:activeAccount.accountId];
  183. }
  184. - (void)presentChatForLocalNotification:(NSDictionary *)userInfo
  185. {
  186. if ([NCConnectionController sharedInstance].appState != kAppStateReady) {
  187. _waitingForServerCapabilities = YES;
  188. _pendingLocalNotification = userInfo;
  189. return;
  190. }
  191. [[NSNotificationCenter defaultCenter] postNotificationName:NCLocalNotificationJoinChatNotification
  192. object:self
  193. userInfo:userInfo];
  194. }
  195. - (void)presentChatForPushNotification:(NCPushNotification *)pushNotification
  196. {
  197. if ([NCConnectionController sharedInstance].appState != kAppStateReady) {
  198. _waitingForServerCapabilities = YES;
  199. _pendingPushNotification = pushNotification;
  200. return;
  201. }
  202. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:pushNotification forKey:@"pushNotification"];
  203. [[NSNotificationCenter defaultCenter] postNotificationName:NCPushNotificationJoinChatNotification
  204. object:self
  205. userInfo:userInfo];
  206. }
  207. - (void)presentAlertForPushNotification:(NCPushNotification *)pushNotification
  208. {
  209. if ([NCConnectionController sharedInstance].appState != kAppStateReady) {
  210. _waitingForServerCapabilities = YES;
  211. _pendingPushNotification = pushNotification;
  212. return;
  213. }
  214. UIAlertController * alert = [UIAlertController
  215. alertControllerWithTitle:[pushNotification bodyForRemoteAlerts]
  216. message:NSLocalizedString(@"Do you want to join this call?", nil)
  217. preferredStyle:UIAlertControllerStyleAlert];
  218. UIAlertAction *joinAudioButton = [UIAlertAction
  219. actionWithTitle:NSLocalizedString(@"Join call (audio only)", nil)
  220. style:UIAlertActionStyleDefault
  221. handler:^(UIAlertAction * _Nonnull action) {
  222. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:pushNotification forKey:@"pushNotification"];
  223. [[NSNotificationCenter defaultCenter] postNotificationName:NCPushNotificationJoinAudioCallAcceptedNotification
  224. object:self
  225. userInfo:userInfo];
  226. }];
  227. UIAlertAction *joinVideoButton = [UIAlertAction
  228. actionWithTitle:NSLocalizedString(@"Join call with video", nil)
  229. style:UIAlertActionStyleDefault
  230. handler:^(UIAlertAction * _Nonnull action) {
  231. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:pushNotification forKey:@"pushNotification"];
  232. [[NSNotificationCenter defaultCenter] postNotificationName:NCPushNotificationJoinVideoCallAcceptedNotification
  233. object:self
  234. userInfo:userInfo];
  235. }];
  236. UIAlertAction* cancelButton = [UIAlertAction
  237. actionWithTitle:NSLocalizedString(@"Cancel", nil)
  238. style:UIAlertActionStyleCancel
  239. handler:nil];
  240. [joinAudioButton setValue:[UIImage systemImageNamed:@"phone"] forKey:@"image"];
  241. [joinVideoButton setValue:[UIImage systemImageNamed:@"video"] forKey:@"image"];
  242. [alert addAction:joinAudioButton];
  243. [alert addAction:joinVideoButton];
  244. [alert addAction:cancelButton];
  245. // Do not show join call dialog until we don't handle 'hangup current call'/'join new one' properly.
  246. if (![NCRoomsManager sharedInstance].callViewController) {
  247. [_mainViewController dismissViewControllerAnimated:NO completion:nil];
  248. [_mainViewController presentViewController:alert animated:YES completion:nil];
  249. } else {
  250. NSLog(@"Not showing join call dialog due to in a call.");
  251. }
  252. }
  253. - (void)presentAlertViewController:(UIAlertController *)alertViewController
  254. {
  255. if (_mainViewController.presentedViewController != nil) {
  256. // When the callview is presented, we need to show the alert this way
  257. [_mainViewController.presentedViewController presentViewController:alertViewController animated:YES completion:nil];
  258. } else {
  259. [_mainViewController presentViewController:alertViewController animated:YES completion:nil];
  260. }
  261. }
  262. - (void)presentAlertIfNotPresentedAlready:(UIAlertController *)alertViewController
  263. {
  264. if (alertViewController != _mainViewController.presentedViewController) {
  265. [self presentAlertViewController:alertViewController];
  266. }
  267. }
  268. - (void)presentAlertWithTitle:(NSString *)title withMessage:(NSString *)message
  269. {
  270. UIAlertController *alertDialog = [UIAlertController alertControllerWithTitle:title
  271. message:message
  272. preferredStyle:UIAlertControllerStyleAlert];
  273. UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:nil];
  274. [alertDialog addAction:okAction];
  275. [self presentAlertViewController:alertDialog];
  276. }
  277. - (void)presentConversationsList
  278. {
  279. [_mainViewController dismissViewControllerAnimated:YES completion:nil];
  280. [_mainViewController popSecondaryColumnToRootViewController];
  281. [_mainViewController showColumn:UISplitViewControllerColumnPrimary];
  282. }
  283. - (void)popToConversationsList
  284. {
  285. [_mainViewController popSecondaryColumnToRootViewController];
  286. [_mainViewController showColumn:UISplitViewControllerColumnPrimary];
  287. }
  288. - (void)presentChatViewController:(ChatViewController *)chatViewController
  289. {
  290. [self presentConversationsList];
  291. [_mainViewController showDetailViewController:chatViewController sender:self];
  292. [_roomsTableViewController setSelectedRoomToken:chatViewController.room.token];
  293. }
  294. - (void)presentCallViewController:(CallViewController *)callViewController withCompletionBlock:(PresentCallControllerCompletionBlock)block
  295. {
  296. [_mainViewController dismissViewControllerAnimated:NO completion:nil];
  297. [_mainViewController presentViewController:callViewController animated:YES completion:^{
  298. if (block) {
  299. block();
  300. }
  301. }];
  302. }
  303. - (void)presentCallKitCallInRoom:(NSString *)token withVideoEnabled:(BOOL)video
  304. {
  305. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:token forKey:@"roomToken"];
  306. [userInfo setValue:@(video) forKey:@"isVideoEnabled"];
  307. if ([NCConnectionController sharedInstance].appState != kAppStateReady) {
  308. _waitingForServerCapabilities = YES;
  309. _pendingCallKitCall = userInfo;
  310. return;
  311. }
  312. [self startCallKitCall:userInfo];
  313. }
  314. - (void)startCallKitCall:(NSMutableDictionary *)callDict
  315. {
  316. NSString *roomToken = [callDict objectForKey:@"roomToken"];
  317. BOOL video = [[callDict objectForKey:@"isVideoEnabled"] boolValue];
  318. [[NCRoomsManager sharedInstance] joinCallWithCallToken:roomToken withVideo:video asInitiator:YES recordingConsent:NO];
  319. }
  320. - (void)presentChatForURL:(NSURLComponents *)urlComponents
  321. {
  322. if ([NCConnectionController sharedInstance].appState != kAppStateReady) {
  323. _waitingForServerCapabilities = YES;
  324. _pendingURL = urlComponents;
  325. return;
  326. }
  327. NSArray *queryItems = urlComponents.queryItems;
  328. NSString *server = [NCUtils valueForKey:@"server" fromQueryItems:queryItems];
  329. NSString *user = [NCUtils valueForKey:@"user" fromQueryItems:queryItems];
  330. NSString *withUser = [NCUtils valueForKey:@"withUser" fromQueryItems:queryItems];
  331. NSString *withRoomToken = [NCUtils valueForKey:@"withRoomToken" fromQueryItems:queryItems];
  332. TalkAccount *account = [[NCDatabaseManager sharedInstance] talkAccountForUserId:user inServer:server];
  333. if (!account) {
  334. [self presentAccountNotConfiguredAlertForUser:user inServer:server];
  335. return;
  336. }
  337. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:account.accountId forKey:@"accountId"];
  338. if (withUser) {
  339. [userInfo setValue:withUser forKey:@"withUser"];
  340. } else if (withRoomToken) {
  341. [userInfo setValue:withRoomToken forKey:@"withRoomToken"];
  342. } else { return; }
  343. [[NSNotificationCenter defaultCenter] postNotificationName:NCURLWantsToOpenConversationNotification
  344. object:self
  345. userInfo:userInfo];
  346. }
  347. - (void)presentSettingsViewController
  348. {
  349. [self presentConversationsList];
  350. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  351. NCNavigationController *settingsNC = [storyboard instantiateViewControllerWithIdentifier:@"settingsNC"];
  352. [self.mainViewController presentViewController:settingsNC animated:YES completion:nil];
  353. }
  354. - (void)presentShareLinkDialogForRoom:(NCRoom *)room inViewContoller:(UITableViewController *)viewController forIndexPath:(NSIndexPath *)indexPath
  355. {
  356. NSString *roomLinkURL = room.linkURL;
  357. if (!roomLinkURL) {
  358. return;
  359. }
  360. NSArray *items = @[roomLinkURL];
  361. UIActivityViewController *controller = [[UIActivityViewController alloc]initWithActivityItems:items applicationActivities:nil];
  362. NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
  363. NSString *emailSubject = [NSString stringWithFormat:NSLocalizedString(@"%@ invitation", nil), appDisplayName];
  364. [controller setValue:emailSubject forKey:@"subject"];
  365. // Presentation on iPads
  366. if (indexPath) {
  367. controller.popoverPresentationController.sourceView = viewController.tableView;
  368. controller.popoverPresentationController.sourceRect = [viewController.tableView rectForRowAtIndexPath:indexPath];
  369. }
  370. [viewController presentViewController:controller animated:YES completion:nil];
  371. controller.completionWithItemsHandler = ^(NSString *activityType,
  372. BOOL completed,
  373. NSArray *returnedItems,
  374. NSError *error) {
  375. if (error) {
  376. NSLog(@"An Error occured sharing room: %@, %@", error.localizedDescription, error.localizedFailureReason);
  377. }
  378. };
  379. }
  380. #pragma mark - Notifications
  381. - (void)appStateHasChanged:(NSNotification *)notification
  382. {
  383. AppState appState = [[notification.userInfo objectForKey:@"appState"] intValue];
  384. if (appState == kAppStateReady && _waitingForServerCapabilities) {
  385. _waitingForServerCapabilities = NO;
  386. if (_pendingPushNotification) {
  387. if (_pendingPushNotification.type == NCPushNotificationTypeCall) {
  388. [self presentAlertForPushNotification:_pendingPushNotification];
  389. } else {
  390. [self presentChatForPushNotification:_pendingPushNotification];
  391. }
  392. } else if (_pendingCallKitCall) {
  393. [self startCallKitCall:_pendingCallKitCall];
  394. } else if (_pendingURL) {
  395. [self presentChatForURL:_pendingURL];
  396. }
  397. }
  398. }
  399. - (void)connectionStateHasChanged:(NSNotification *)notification
  400. {
  401. ConnectionState connectionState = [[notification.userInfo objectForKey:@"connectionState"] intValue];
  402. switch (connectionState) {
  403. case kConnectionStateDisconnected:
  404. [[JDStatusBarNotificationPresenter sharedPresenter] presentWithText:NSLocalizedString(@"Network not available", nil) dismissAfterDelay:4.0f includedStyle:JDStatusBarNotificationIncludedStyleError];
  405. break;
  406. case kConnectionStateConnected:
  407. [[JDStatusBarNotificationPresenter sharedPresenter] presentWithText:NSLocalizedString(@"Network available", nil) dismissAfterDelay:4.0f includedStyle:JDStatusBarNotificationIncludedStyleSuccess];
  408. break;
  409. default:
  410. break;
  411. }
  412. }
  413. #pragma mark - LoginViewControllerDelegate
  414. - (void)loginViewControllerDidFinish:(LoginViewController *)viewController
  415. {
  416. [_mainViewController dismissViewControllerAnimated:YES completion:^{
  417. [[NCConnectionController sharedInstance] checkAppState];
  418. // Get server capabilities again to check if user is allowed to use Nextcloud Talk
  419. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  420. [[NCSettingsController sharedInstance] getCapabilitiesForAccountId:activeAccount.accountId withCompletionBlock:nil];
  421. }];
  422. }
  423. #pragma mark - AuthenticationViewControllerDelegate
  424. - (void)authenticationViewControllerDidFinish:(AuthenticationViewController *)viewController
  425. {
  426. [_mainViewController dismissViewControllerAnimated:YES completion:^{
  427. [[NCConnectionController sharedInstance] checkAppState];
  428. // Get server capabilities again to check if user is allowed to use Nextcloud Talk
  429. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  430. [[NCSettingsController sharedInstance] getCapabilitiesForAccountId:activeAccount.accountId withCompletionBlock:nil];
  431. }];
  432. }
  433. @end