ShareViewController.m 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "ShareViewController.h"
  6. #import <Intents/Intents.h>
  7. #import <MobileCoreServices/MobileCoreServices.h>
  8. #import "NCAPIController.h"
  9. #import "NCAppBranding.h"
  10. #import "NCDatabaseManager.h"
  11. #import "NCIntentController.h"
  12. #import "NCRoom.h"
  13. #import "PlaceholderView.h"
  14. #import "ShareTableViewCell.h"
  15. #import "NCKeyChainController.h"
  16. #import "NextcloudTalk-Swift.h"
  17. @interface ShareViewController () <UISearchControllerDelegate, UISearchResultsUpdating, ShareConfirmationViewControllerDelegate>
  18. {
  19. UISearchController *_searchController;
  20. UITableViewController *_resultTableViewController;
  21. NSMutableArray *_filteredRooms;
  22. NSMutableArray *_rooms;
  23. PlaceholderView *_roomsBackgroundView;
  24. PlaceholderView *_roomSearchBackgroundView;
  25. TalkAccount *_shareAccount;
  26. ServerCapabilities *_serverCapabilities;
  27. RLMRealm *_realm;
  28. }
  29. @end
  30. @implementation ShareViewController
  31. - (id)initToForwardMessage:(NSString *)message fromChatViewController:(UIViewController *)chatViewController
  32. {
  33. self = [super init];
  34. if (self) {
  35. self.chatViewController = chatViewController;
  36. self.forwardMessage = message;
  37. self.forwarding = YES;
  38. }
  39. return self;
  40. }
  41. - (id)initToForwardObjectShareMessage:(NCChatMessage *)objectShareMessage fromChatViewController:(UIViewController *)chatViewController
  42. {
  43. self = [super init];
  44. if (self) {
  45. self.chatViewController = chatViewController;
  46. self.forwardObjectShareMessage = objectShareMessage;
  47. self.forwarding = YES;
  48. }
  49. return self;
  50. }
  51. - (void)viewDidLoad
  52. {
  53. [super viewDidLoad];
  54. _filteredRooms = [[NSMutableArray alloc] init];
  55. // Configure database
  56. NSString *path = [[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupIdentifier] URLByAppendingPathComponent:kTalkDatabaseFolder] path];
  57. NSURL *databaseURL = [[NSURL fileURLWithPath:path] URLByAppendingPathComponent:kTalkDatabaseFileName];
  58. if ([[NSFileManager defaultManager] fileExistsAtPath:databaseURL.path]) {
  59. @try {
  60. NSError *error = nil;
  61. // schemaVersionAtURL throws an exception when file is not readable
  62. uint64_t currentSchemaVersion = [RLMRealm schemaVersionAtURL:databaseURL encryptionKey:nil error:&error];
  63. if (error || currentSchemaVersion != kTalkDatabaseSchemaVersion) {
  64. NSLog(@"Current schemaVersion is %llu app schemaVersion is %llu", currentSchemaVersion, kTalkDatabaseSchemaVersion);
  65. NSLog(@"Database needs migration -> don't open database from extension");
  66. NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  67. [self.extensionContext cancelRequestWithError:error];
  68. return;
  69. } else {
  70. NSLog(@"Current schemaVersion is %llu app schemaVersion is %llu", currentSchemaVersion, kTalkDatabaseSchemaVersion);
  71. }
  72. }
  73. @catch (NSException *exception) {
  74. NSLog(@"Reading schemaVersion failed: %@", exception.reason);
  75. NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  76. [self.extensionContext cancelRequestWithError:error];
  77. return;
  78. }
  79. } else {
  80. NSLog(@"Database does not exist -> main app needs to run before extension.");
  81. NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  82. [self.extensionContext cancelRequestWithError:error];
  83. return;
  84. }
  85. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  86. configuration.fileURL = databaseURL;
  87. configuration.schemaVersion= kTalkDatabaseSchemaVersion;
  88. configuration.objectClasses = @[TalkAccount.class, NCRoom.class, ServerCapabilities.class, FederatedCapabilities.class];
  89. configuration.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  90. // At the very minimum we need to update the version with an empty block to indicate that the schema has been upgraded (automatically) by Realm
  91. };
  92. NSError *error = nil;
  93. // When running as an extension, set the default configuration to make sure we always use the correct realm-file
  94. if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) {
  95. [RLMRealmConfiguration setDefaultConfiguration:configuration];
  96. }
  97. _realm = [RLMRealm realmWithConfiguration:configuration error:&error];
  98. if (self.extensionContext && self.extensionContext.intent && [self.extensionContext.intent isKindOfClass:[INSendMessageIntent class]]) {
  99. INSendMessageIntent *intent = (INSendMessageIntent *)self.extensionContext.intent;
  100. NSPredicate *query = [NSPredicate predicateWithFormat:@"internalId = %@", intent.conversationIdentifier];
  101. NCRoom *managedRoom = [NCRoom objectsInRealm:_realm withPredicate:query].firstObject;
  102. if (managedRoom) {
  103. NCRoom *room = [[NCRoom alloc] initWithValue:managedRoom];
  104. if ([room isFederated]) {
  105. [self showFederatedAlert];
  106. NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  107. [self.extensionContext cancelRequestWithError:error];
  108. return;
  109. }
  110. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", room.accountId];
  111. TalkAccount *managedAccount = [TalkAccount objectsInRealm:_realm withPredicate:query].firstObject;
  112. if (managedAccount) {
  113. TalkAccount *intentAccount = [[TalkAccount alloc] initWithValue:managedAccount];
  114. [self setupShareViewForAccount:intentAccount];
  115. ShareConfirmationViewController *shareConfirmationVC = [[ShareConfirmationViewController alloc] initWithRoom:room account:intentAccount serverCapabilities:_serverCapabilities];
  116. shareConfirmationVC.delegate = self;
  117. shareConfirmationVC.isModal = YES;
  118. [self setSharedItemToShareConfirmationViewController:shareConfirmationVC];
  119. [self.navigationController pushViewController:shareConfirmationVC animated:YES];
  120. return;
  121. }
  122. }
  123. }
  124. [self setupShareViewForAccount:nil];
  125. // Configure table views
  126. NSBundle *bundle = [NSBundle bundleForClass:[ShareTableViewCell class]];
  127. [self.tableView registerNib:[UINib nibWithNibName:kShareTableCellNibName bundle:bundle] forCellReuseIdentifier:kShareCellIdentifier];
  128. self.tableView.separatorInset = UIEdgeInsetsMake(0, 60, 0, 0);
  129. self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
  130. _resultTableViewController = [[UITableViewController alloc] init];
  131. _resultTableViewController.tableView.delegate = self;
  132. _resultTableViewController.tableView.dataSource = self;
  133. [_resultTableViewController.tableView registerNib:[UINib nibWithNibName:kShareTableCellNibName bundle:bundle] forCellReuseIdentifier:kShareCellIdentifier];
  134. _resultTableViewController.tableView.separatorInset = UIEdgeInsetsMake(0, 60, 0, 0);
  135. _resultTableViewController.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
  136. _searchController = [[UISearchController alloc] initWithSearchResultsController:_resultTableViewController];
  137. _searchController.delegate = self;
  138. _searchController.searchResultsUpdater = self;
  139. [_searchController.searchBar sizeToFit];
  140. // Configure navigation bar
  141. self.navigationItem.title = _forwarding ? NSLocalizedString(@"Forward to", nil) : NSLocalizedString(@"Share with", nil);
  142. [self.navigationController.navigationBar setTitleTextAttributes:
  143. @{NSForegroundColorAttributeName:[NCAppBranding themeTextColor]}];
  144. self.navigationController.navigationBar.tintColor = [NCAppBranding themeTextColor];
  145. self.navigationController.navigationBar.translucent = NO;
  146. self.navigationController.navigationBar.barTintColor = [NCAppBranding themeColor];
  147. UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
  148. target:self action:@selector(cancelButtonPressed)];
  149. cancelButton.accessibilityHint = NSLocalizedString(@"Double tap to dismiss sharing options", nil);
  150. self.navigationController.navigationBar.topItem.leftBarButtonItem = cancelButton;
  151. UIColor *themeColor = [NCAppBranding themeColor];
  152. UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init];
  153. [appearance configureWithOpaqueBackground];
  154. appearance.backgroundColor = themeColor;
  155. appearance.titleTextAttributes = @{NSForegroundColorAttributeName:[NCAppBranding themeTextColor]};
  156. self.navigationItem.standardAppearance = appearance;
  157. self.navigationItem.compactAppearance = appearance;
  158. self.navigationItem.scrollEdgeAppearance = appearance;
  159. self.navigationItem.searchController = _searchController;
  160. self.navigationItem.searchController.searchBar.searchTextField.backgroundColor = [NCUtils searchbarBGColorForColor:themeColor];
  161. if (@available(iOS 16.0, *)) {
  162. self.navigationItem.preferredSearchBarPlacement = UINavigationItemSearchBarPlacementStacked;
  163. }
  164. _searchController.searchBar.tintColor = [NCAppBranding themeTextColor];
  165. UITextField *searchTextField = [_searchController.searchBar valueForKey:@"searchField"];
  166. UIButton *clearButton = [searchTextField valueForKey:@"_clearButton"];
  167. searchTextField.tintColor = [NCAppBranding themeTextColor];
  168. searchTextField.textColor = [NCAppBranding themeTextColor];
  169. dispatch_async(dispatch_get_main_queue(), ^{
  170. // Search bar placeholder
  171. searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"Search", nil)
  172. attributes:@{NSForegroundColorAttributeName:[[NCAppBranding themeTextColor] colorWithAlphaComponent:0.5]}];
  173. // Search bar search icon
  174. UIImageView *searchImageView = (UIImageView *)searchTextField.leftView;
  175. searchImageView.image = [searchImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  176. [searchImageView setTintColor:[[NCAppBranding themeTextColor] colorWithAlphaComponent:0.5]];
  177. // Search bar search clear button
  178. UIImage *clearButtonImage = [clearButton.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  179. [clearButton setImage:clearButtonImage forState:UIControlStateNormal];
  180. [clearButton setImage:clearButtonImage forState:UIControlStateHighlighted];
  181. [clearButton setTintColor:[NCAppBranding themeTextColor]];
  182. });
  183. // Place resultTableViewController correctly
  184. self.definesPresentationContext = YES;
  185. // Rooms placeholder view
  186. _roomsBackgroundView = [[PlaceholderView alloc] init];
  187. [_roomsBackgroundView setImage:[UIImage imageNamed:@"conversations-placeholder"]];
  188. [_roomsBackgroundView.placeholderTextView setText:NSLocalizedString(@"You are not part of any conversation", nil)];
  189. [_roomsBackgroundView.placeholderView setHidden:(_rooms.count > 0)];
  190. [_roomsBackgroundView.loadingView setHidden:YES];
  191. self.tableView.backgroundView = _roomsBackgroundView;
  192. _roomSearchBackgroundView = [[PlaceholderView alloc] init];
  193. [_roomSearchBackgroundView setImage:[UIImage imageNamed:@"conversations-placeholder"]];
  194. [_roomSearchBackgroundView.placeholderTextView setText:NSLocalizedString(@"No results found", nil)];
  195. [_roomSearchBackgroundView.placeholderView setHidden:YES];
  196. [_roomSearchBackgroundView.loadingView setHidden:YES];
  197. _resultTableViewController.tableView.backgroundView = _roomSearchBackgroundView;
  198. // Fix uisearchcontroller animation
  199. self.extendedLayoutIncludesOpaqueBars = YES;
  200. }
  201. - (ServerCapabilities *)getServerCapabilitesForAccount:(TalkAccount *)account withRealm:(RLMRealm *)realm;
  202. {
  203. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId];
  204. ServerCapabilities *managedServerCapabilities = [ServerCapabilities objectsInRealm:realm withPredicate:query].firstObject;
  205. if (managedServerCapabilities) {
  206. return [[ServerCapabilities alloc] initWithValue:managedServerCapabilities];
  207. }
  208. return nil;
  209. }
  210. #pragma mark - Navigation buttons
  211. - (void)cancelButtonPressed
  212. {
  213. [self.delegate shareViewControllerDidCancel:self];
  214. NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
  215. [self.extensionContext cancelRequestWithError:error];
  216. }
  217. #pragma mark - Accounts
  218. - (void)setProfileButtonForAccount:(TalkAccount *)account
  219. {
  220. UIButton *profileButton = [UIButton buttonWithType:UIButtonTypeCustom];
  221. [profileButton addTarget:self action:@selector(showAccountSelector) forControlEvents:UIControlEventTouchUpInside];
  222. profileButton.frame = CGRectMake(0, 0, 30, 30);
  223. profileButton.accessibilityLabel = NSLocalizedString(@"User profile and settings", nil);
  224. profileButton.accessibilityHint = NSLocalizedString(@"Double tap to go to user profile and application settings", nil);
  225. UIImage *profileImage = [[NCAPIController sharedInstance] userProfileImageForAccount:account withStyle:self.traitCollection.userInterfaceStyle];
  226. if (profileImage) {
  227. UIGraphicsBeginImageContextWithOptions(profileButton.bounds.size, NO, 3.0);
  228. [[UIBezierPath bezierPathWithRoundedRect:profileButton.bounds cornerRadius:profileButton.bounds.size.height] addClip];
  229. [profileImage drawInRect:profileButton.bounds];
  230. profileImage = UIGraphicsGetImageFromCurrentImageContext();
  231. UIGraphicsEndImageContext();
  232. [profileButton setImage:profileImage forState:UIControlStateNormal];
  233. } else {
  234. UIImage *profileImage = [UIImage systemImageNamed:@"person"];
  235. [profileButton setImage:profileImage forState:UIControlStateNormal];
  236. profileButton.contentMode = UIViewContentModeCenter;
  237. }
  238. UIBarButtonItem *profileBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:profileButton];
  239. NSLayoutConstraint *width = [profileBarButtonItem.customView.widthAnchor constraintEqualToConstant:30];
  240. width.active = YES;
  241. NSLayoutConstraint *height = [profileBarButtonItem.customView.heightAnchor constraintEqualToConstant:30];
  242. height.active = YES;
  243. [self.navigationItem setRightBarButtonItem:profileBarButtonItem];
  244. }
  245. - (void)setupShareViewForAccount:(TalkAccount *)account
  246. {
  247. _shareAccount = account;
  248. if (!account) {
  249. TalkAccount *managedActiveAccount = [TalkAccount objectsInRealm:_realm where:(@"active = true")].firstObject;
  250. if (managedActiveAccount) {
  251. _shareAccount = [[TalkAccount alloc] initWithValue:managedActiveAccount];
  252. } else {
  253. // No account is configured in the app yet
  254. return;
  255. }
  256. }
  257. // Show account button selector if there are more than one account
  258. if ([TalkAccount allObjectsInRealm:_realm].count > 1) {
  259. [self setProfileButtonForAccount:_shareAccount];
  260. }
  261. NSArray *accountRooms = [self roomsForAccountId:_shareAccount.accountId];
  262. _rooms = [[NSMutableArray alloc] initWithArray:accountRooms];
  263. _serverCapabilities = [self getServerCapabilitesForAccount:_shareAccount withRealm:_realm];
  264. [self.tableView reloadData];
  265. }
  266. - (void)showAccountSelector
  267. {
  268. UIAlertController *optionsActionSheet =
  269. [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Accounts", nil)
  270. message:nil
  271. preferredStyle:UIAlertControllerStyleActionSheet];
  272. NSMutableArray *allAccounts = [NSMutableArray new];
  273. for (TalkAccount *managedAccount in [TalkAccount allObjectsInRealm:_realm]) {
  274. TalkAccount *account = [[TalkAccount alloc] initWithValue:managedAccount];
  275. [allAccounts addObject:account];
  276. UIAlertAction *action = [UIAlertAction actionWithTitle:account.userDisplayName
  277. style:UIAlertActionStyleDefault
  278. handler:^void (UIAlertAction *action) {
  279. [self setupShareViewForAccount:account];
  280. }];
  281. if ([_shareAccount.accountId isEqualToString:account.accountId]) {
  282. [action setValue:[[UIImage imageNamed:@"checkmark"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forKey:@"image"];
  283. }
  284. [optionsActionSheet addAction:action];
  285. }
  286. [optionsActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
  287. // Presentation on iPads
  288. optionsActionSheet.popoverPresentationController.barButtonItem = self.navigationItem.rightBarButtonItem;
  289. [self presentViewController:optionsActionSheet animated:YES completion:nil];
  290. }
  291. #pragma mark - Rooms
  292. - (NSArray *)roomsForAccountId:(NSString *)accountId
  293. {
  294. NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", accountId];
  295. RLMResults *managedRooms = [NCRoom objectsInRealm:_realm withPredicate:query];;
  296. // Create an unmanaged copy of the rooms
  297. NSMutableArray *unmanagedRooms = [NSMutableArray new];
  298. for (NCRoom *managedRoom in managedRooms) {
  299. NCRoom *unmanagedRoom = [[NCRoom alloc] initWithValue:managedRoom];
  300. // Filter out breakout rooms with lobby enabled
  301. if ([unmanagedRoom isBreakoutRoom] && unmanagedRoom.lobbyState == NCRoomLobbyStateModeratorsOnly) {
  302. continue;
  303. }
  304. [unmanagedRooms addObject:unmanagedRoom];
  305. }
  306. // Sort by favorites
  307. NSSortDescriptor *favoriteSorting = [NSSortDescriptor sortDescriptorWithKey:@"" ascending:YES comparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
  308. NCRoom *first = (NCRoom*)obj1;
  309. NCRoom *second = (NCRoom*)obj2;
  310. BOOL favorite1 = first.isFavorite;
  311. BOOL favorite2 = second.isFavorite;
  312. if (favorite1 != favorite2) {
  313. return favorite2 - favorite1;
  314. }
  315. return NSOrderedSame;
  316. }];
  317. // Sort by lastActivity
  318. NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastActivity" ascending:NO];
  319. NSArray *descriptors = [NSArray arrayWithObjects:favoriteSorting, valueDescriptor, nil];
  320. [unmanagedRooms sortUsingDescriptors:descriptors];
  321. return unmanagedRooms;
  322. }
  323. #pragma mark - Shared items
  324. - (void)setSharedItemToShareConfirmationViewController:(ShareConfirmationViewController *)shareConfirmationVC
  325. {
  326. NSLog(@"Received %lu files to share", [self.extensionContext.inputItems count]);
  327. [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull extItem, NSUInteger idx, BOOL * _Nonnull stop) {
  328. [extItem.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {
  329. // Check if shared video
  330. if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie]) {
  331. [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeMovie
  332. options:nil
  333. completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
  334. if ([(NSObject *)item isKindOfClass:[NSURL class]]) {
  335. NSLog(@"Shared Video = %@", item);
  336. NSURL *videoURL = (NSURL *)item;
  337. [shareConfirmationVC.shareItemController addItemWithURL:videoURL];
  338. }
  339. }];
  340. return;
  341. }
  342. // Check if shared file
  343. // Make sure this is checked before image! Otherwise sharing images from Mail won't work >= iOS 13
  344. if ([itemProvider hasItemConformingToTypeIdentifier:@"public.file-url"]) {
  345. [itemProvider loadItemForTypeIdentifier:@"public.file-url"
  346. options:nil
  347. completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
  348. if ([(NSObject *)item isKindOfClass:[NSURL class]]) {
  349. NSLog(@"Shared File URL = %@", item);
  350. NSURL *fileURL = (NSURL *)item;
  351. [shareConfirmationVC.shareItemController addItemWithURL:fileURL];
  352. }
  353. }];
  354. return;
  355. }
  356. // Check if shared image
  357. if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
  358. [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage
  359. options:nil
  360. completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
  361. if ([(NSObject *)item isKindOfClass:[NSURL class]]) {
  362. NSLog(@"Shared Image = %@", item);
  363. NSURL *imageURL = (NSURL *)item;
  364. [shareConfirmationVC.shareItemController addItemWithURL:imageURL];
  365. } else if ([(NSObject *)item isKindOfClass:[UIImage class]]) {
  366. // Occurs when sharing a screenshot
  367. NSLog(@"Shared UIImage = %@", item);
  368. UIImage *image = (UIImage *)item;
  369. [shareConfirmationVC.shareItemController addItemWithImage:image];
  370. }
  371. }];
  372. return;
  373. }
  374. // Check if shared URL
  375. if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"]) {
  376. [itemProvider loadItemForTypeIdentifier:@"public.url"
  377. options:nil
  378. completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
  379. if ([(NSObject *)item isKindOfClass:[NSURL class]]) {
  380. NSLog(@"Shared URL = %@", item);
  381. NSURL *sharedURL = (NSURL *)item;
  382. [shareConfirmationVC shareText:sharedURL.absoluteString];
  383. }
  384. }];
  385. }
  386. // Check if shared text
  387. if ([itemProvider hasItemConformingToTypeIdentifier:@"public.plain-text"]) {
  388. [itemProvider loadItemForTypeIdentifier:@"public.plain-text"
  389. options:nil
  390. completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
  391. if ([(NSObject *)item isKindOfClass:[NSString class]]) {
  392. NSLog(@"Shared Text = %@", item);
  393. NSString *sharedText = (NSString *)item;
  394. [shareConfirmationVC shareText:sharedText];
  395. }
  396. }];
  397. }
  398. // Check if vcard
  399. if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeVCard]) {
  400. [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeVCard
  401. options:nil
  402. completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
  403. if ([(NSObject *)item isKindOfClass:[NSData class]]) {
  404. NSLog(@"Shared Contact = %@", item);
  405. NSData *contactData = (NSData *)item;
  406. [shareConfirmationVC.shareItemController addItemWithContactData:contactData];
  407. }
  408. }];
  409. }
  410. }];
  411. }];
  412. }
  413. #pragma mark - Search controller
  414. - (void)updateSearchResultsForSearchController:(UISearchController *)searchController
  415. {
  416. [self searchForRoomsWithString:_searchController.searchBar.text];
  417. }
  418. - (void)searchForRoomsWithString:(NSString *)searchString
  419. {
  420. NSArray *filteredRooms = [self filterRoomsWithString:searchString];
  421. _filteredRooms = [[NSMutableArray alloc] initWithArray:filteredRooms];
  422. [_roomSearchBackgroundView.placeholderView setHidden:(_filteredRooms.count > 0)];
  423. [_resultTableViewController.tableView reloadData];
  424. }
  425. - (NSArray *)filterRoomsWithString:(NSString *)searchString
  426. {
  427. NSPredicate *sPredicate = [NSPredicate predicateWithFormat:@"displayName CONTAINS[c] %@", searchString];
  428. return [_rooms filteredArrayUsingPredicate:sPredicate];
  429. }
  430. #pragma mark - ShareConfirmationViewController Delegate
  431. - (void)shareConfirmationViewControllerDidFailed:(ShareConfirmationViewController *)viewController
  432. {
  433. [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
  434. }
  435. - (void)shareConfirmationViewControllerDidFinish:(ShareConfirmationViewController *)viewController
  436. {
  437. [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
  438. }
  439. #pragma mark - Table view data source
  440. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  441. {
  442. return 1;
  443. }
  444. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  445. {
  446. if (tableView == _resultTableViewController.tableView) {
  447. return _filteredRooms.count;
  448. }
  449. return _rooms.count;
  450. }
  451. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  452. {
  453. return kShareTableCellHeight;
  454. }
  455. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  456. {
  457. NCRoom *room = [_rooms objectAtIndex:indexPath.row];
  458. if (tableView == _resultTableViewController.tableView) {
  459. room = [_filteredRooms objectAtIndex:indexPath.row];
  460. }
  461. ShareTableViewCell *cell = (ShareTableViewCell *)[tableView dequeueReusableCellWithIdentifier:kShareCellIdentifier];
  462. if (!cell) {
  463. cell = [[ShareTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kShareCellIdentifier];
  464. }
  465. cell.titleLabel.text = room.displayName;
  466. [cell.avatarImageView setAvatarFor:room];
  467. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  468. return cell;
  469. }
  470. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  471. {
  472. [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
  473. NCRoom *room = [_rooms objectAtIndex:indexPath.row];
  474. if (tableView == _resultTableViewController.tableView) {
  475. room = [_filteredRooms objectAtIndex:indexPath.row];
  476. }
  477. if ([room isFederated]) {
  478. [self showFederatedAlert];
  479. return;
  480. }
  481. BOOL hasChatPermission = ![[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityChatPermission] || (room.permissions & NCPermissionChat) != 0;
  482. if (!hasChatPermission || room.readOnlyState == NCRoomReadOnlyStateReadOnly) {
  483. [self showChatPermissionAlert];
  484. return;
  485. }
  486. ShareConfirmationViewController *shareConfirmationVC = [[ShareConfirmationViewController alloc] initWithRoom:room account:_shareAccount serverCapabilities:_serverCapabilities];
  487. shareConfirmationVC.delegate = self;
  488. if (_forwardMessage) {
  489. shareConfirmationVC.delegate = (id<ShareConfirmationViewControllerDelegate>)_chatViewController;
  490. shareConfirmationVC.forwardingMessage = YES;
  491. [shareConfirmationVC shareText:_forwardMessage];
  492. } else if (_forwardObjectShareMessage) {
  493. shareConfirmationVC.delegate = (id<ShareConfirmationViewControllerDelegate>)_chatViewController;
  494. shareConfirmationVC.forwardingMessage = YES;
  495. [shareConfirmationVC shareObjectShareMessage:_forwardObjectShareMessage];
  496. } else {
  497. [self setSharedItemToShareConfirmationViewController:shareConfirmationVC];
  498. }
  499. [self.navigationController pushViewController:shareConfirmationVC animated:YES];
  500. }
  501. - (void)showChatPermissionAlert
  502. {
  503. UIAlertController * alert = [UIAlertController
  504. alertControllerWithTitle:NSLocalizedString(@"Cannot share to conversation", nil)
  505. message:NSLocalizedString(@"Either you don't have chat permission or the conversation is read-only.", nil)
  506. preferredStyle:UIAlertControllerStyleAlert];
  507. UIAlertAction* okButton = [UIAlertAction
  508. actionWithTitle:NSLocalizedString(@"OK", nil)
  509. style:UIAlertActionStyleDefault
  510. handler:nil];
  511. [alert addAction:okButton];
  512. [self presentViewController:alert animated:YES completion:nil];
  513. }
  514. - (void)showFederatedAlert
  515. {
  516. UIAlertController * alert = [UIAlertController
  517. alertControllerWithTitle:NSLocalizedString(@"Cannot share to conversation", nil)
  518. message:NSLocalizedString(@"Sharing to a federated conversation is not supported.", nil)
  519. preferredStyle:UIAlertControllerStyleAlert];
  520. UIAlertAction* okButton = [UIAlertAction
  521. actionWithTitle:NSLocalizedString(@"OK", nil)
  522. style:UIAlertActionStyleDefault
  523. handler:nil];
  524. [alert addAction:okButton];
  525. [self presentViewController:alert animated:YES completion:nil];
  526. }
  527. @end