chatsecureios / ChatSecure / Classes / View Controllers / OTRComposeViewController.m @ 1e17d74e
History | View | Annotate | Download (29.2 KB)
1 |
// |
---|---|
2 |
// OTRComposeViewController.m |
3 |
// Off the Record |
4 |
// |
5 |
// Created by David Chiles on 3/4/14. |
6 |
// Copyright (c) 2014 Chris Ballinger. All rights reserved. |
7 |
// |
8 |
|
9 |
#import "OTRComposeViewController.h" |
10 |
|
11 |
#import "OTRBuddy.h" |
12 |
#import "OTRAccount.h" |
13 |
#import "OTRDatabaseView.h" |
14 |
#import "OTRLog.h" |
15 |
#import "OTRDatabaseManager.h" |
16 |
#import "OTRDatabaseView.h" |
17 |
#import "OTRAccountsManager.h" |
18 |
@import YapDatabase; |
19 |
@import PureLayout; |
20 |
@import BButton; |
21 |
#import "OTRBuddyInfoCell.h" |
22 |
#import "OTRXMPPManager_Private.h" |
23 |
#import "OTRNewBuddyViewController.h" |
24 |
#import "OTRChooseAccountViewController.h" |
25 |
#import "UITableView+ChatSecure.h" |
26 |
|
27 |
#import <ChatSecureCore/ChatSecureCore-Swift.h> |
28 |
|
29 |
@import OTRAssets; |
30 |
|
31 |
@interface OTRComposeViewController () <UITableViewDataSource, UITableViewDelegate, OTRYapViewHandlerDelegateProtocol, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, OTRComposeGroupViewControllerDelegate> |
32 |
|
33 |
@property (nonatomic, strong) UITableView *tableView; |
34 |
@property (nonatomic, strong) OTRVerticalStackView *tableViewHeader; |
35 |
@property (nonatomic, strong) NSLayoutConstraint * tableViewBottomConstraint; |
36 |
@property (nonatomic, strong) OTRYapViewHandler *viewHandler; |
37 |
@property (nonatomic, strong) OTRYapViewHandler *searchViewHandler; |
38 |
@property (nonatomic, strong) UISearchController *searchController; |
39 |
@property (nonatomic, strong, readonly) YapDatabaseConnection *searchConnection; |
40 |
@property (nonatomic, strong, readonly) YapDatabaseSearchQueue *searchQueue; |
41 |
@property (nonatomic, weak, readonly) YapDatabase *database; |
42 |
@property (nonatomic, weak) YapDatabaseConnection *readWriteConnection; |
43 |
@property (nonatomic, strong) NSMutableSet <NSString *>*selectedBuddiesIdSet; |
44 |
|
45 |
@property (nonatomic, strong) UIBarButtonItem *doneBarButtonItem; |
46 |
@property (nonatomic, strong) UIBarButtonItem *groupBarButtonItem; |
47 |
@property (nonatomic, strong) UISegmentedControl *inboxArchiveControl; |
48 |
@end |
49 |
|
50 |
@implementation OTRComposeViewController |
51 |
|
52 |
- (instancetype)init { |
53 |
if (self = [super init]) { |
54 |
self.selectedBuddiesIdSet = [[NSMutableSet alloc] init]; |
55 |
_database = [OTRDatabaseManager sharedInstance].database; |
56 |
_readWriteConnection = [OTRDatabaseManager sharedInstance].readWriteDatabaseConnection; |
57 |
_searchConnection = [self.database newConnection]; |
58 |
_searchConnection.name = @"ComposeViewSearchConnection"; |
59 |
_searchQueue = [[YapDatabaseSearchQueue alloc] init]; |
60 |
_selectionModeIsSingle = YES; |
61 |
} |
62 |
return self; |
63 |
} |
64 |
|
65 |
- (void)viewDidLoad |
66 |
{ |
67 |
[super viewDidLoad]; |
68 |
|
69 |
self.view.backgroundColor = [UIColor whiteColor]; |
70 |
|
71 |
/////////// Navigation Bar /////////// |
72 |
self.title = COMPOSE_STRING(); |
73 |
|
74 |
UIBarButtonItem * cancelBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonPressed:)]; |
75 |
|
76 |
UIImage *checkImage = [UIImage imageNamed:@"ic-check" inBundle:[OTRAssets resourcesBundle] compatibleWithTraitCollection:nil]; |
77 |
self.doneBarButtonItem = [[UIBarButtonItem alloc] initWithImage:checkImage style:UIBarButtonItemStylePlain target:self action:@selector(doneButtonPressed:)]; |
78 |
|
79 |
self.groupBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:GROUP_CHAT_STRING() style:UIBarButtonItemStylePlain target:self action:@selector(groupButtonPressed:)]; |
80 |
|
81 |
|
82 |
self.navigationItem.leftBarButtonItem = cancelBarButtonItem; |
83 |
self.navigationItem.rightBarButtonItem = self.groupBarButtonItem; |
84 |
|
85 |
_inboxArchiveControl = [[UISegmentedControl alloc] initWithItems:@[ACTIVE_BUDDIES_STRING(), ARCHIVE_STRING()]]; |
86 |
_inboxArchiveControl.selectedSegmentIndex = 0; |
87 |
[self updateInboxArchiveFilteringAndShowArchived:NO]; |
88 |
[_inboxArchiveControl addTarget:self action:@selector(inboxArchiveControlValueChanged:) forControlEvents:UIControlEventValueChanged]; |
89 |
self.navigationItem.titleView = _inboxArchiveControl; |
90 |
|
91 |
/////////// TableView /////////// |
92 |
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; |
93 |
self.tableView.translatesAutoresizingMaskIntoConstraints = NO; |
94 |
self.tableView.delegate = self; |
95 |
self.tableView.dataSource = self; |
96 |
self.tableView.rowHeight = OTRBuddyInfoCellHeight; |
97 |
[self.view addSubview:self.tableView]; |
98 |
|
99 |
self.tableViewHeader = [[OTRVerticalStackView alloc] init]; |
100 |
[self.tableViewHeader setBackgroundColor:UIColor.groupTableViewBackgroundColor]; |
101 |
self.tableView.tableHeaderView = self.tableViewHeader; |
102 |
|
103 |
// Add the "Add friends" button |
104 |
UITableViewCell *cellAddFriends = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; |
105 |
cellAddFriends.textLabel.text = ADD_BUDDY_STRING(); |
106 |
cellAddFriends.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
107 |
__weak typeof(self)weakSelf = self; |
108 |
[self.tableViewHeader addStackedSubview:cellAddFriends identifier:ADD_BUDDY_STRING() gravity:OTRVerticalStackViewGravityMiddle height:80 callback:^() { |
109 |
// TODO: we should migrate to a persistent queue so when |
110 |
// you add a buddy offline it will eventually work |
111 |
// See: https://github.com/ChatSecure/ChatSecure-iOS/issues/679 |
112 |
NSArray *accounts = [OTRAccountsManager allAccounts]; |
113 |
__strong typeof(weakSelf)strongSelf = weakSelf; |
114 |
[strongSelf addBuddy:accounts]; |
115 |
}]; |
116 |
// Add the "Join Group" button |
117 |
UITableViewCell *cellJoinGroup = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; |
118 |
cellJoinGroup.textLabel.text = JOIN_GROUP_STRING(); |
119 |
cellJoinGroup.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
120 |
[self.tableViewHeader addStackedSubview:cellJoinGroup identifier:JOIN_GROUP_STRING() gravity:OTRVerticalStackViewGravityBottom height:80 callback:^() { |
121 |
__strong typeof(weakSelf)strongSelf = weakSelf; |
122 |
[strongSelf joinGroup:cellJoinGroup]; |
123 |
}]; |
124 |
|
125 |
[self.tableView registerClass:[OTRBuddyInfoCell class] forCellReuseIdentifier:[OTRBuddyInfoCell reuseIdentifier]]; |
126 |
|
127 |
[self.tableView autoPinEdgesToSuperviewEdges]; |
128 |
|
129 |
[self setupSearchController]; |
130 |
|
131 |
//////// View Handlers ///////// |
132 |
self.viewHandler = [[OTRYapViewHandler alloc] initWithDatabaseConnection:[OTRDatabaseManager sharedInstance].longLivedReadOnlyConnection databaseChangeNotificationName:[DatabaseNotificationName LongLivedTransactionChanges]]; |
133 |
self.viewHandler.delegate = self; |
134 |
[self.viewHandler setup:OTRArchiveFilteredBuddiesName groups:@[OTRBuddyGroup]]; |
135 |
|
136 |
self.searchViewHandler = [[OTRYapViewHandler alloc] initWithDatabaseConnection:[OTRDatabaseManager sharedInstance].longLivedReadOnlyConnection databaseChangeNotificationName:[DatabaseNotificationName LongLivedTransactionChanges]]; |
137 |
self.searchViewHandler.delegate = self; |
138 |
NSString *searchViewName = [YapDatabaseConstants extensionName:DatabaseExtensionNameBuddySearchResultsViewName]; |
139 |
[self.searchViewHandler setup:searchViewName groupBlock:^BOOL(NSString * _Nonnull group, YapDatabaseReadTransaction * _Nonnull transaction) { |
140 |
return YES; |
141 |
} sortBlock:^NSComparisonResult(NSString * _Nonnull group1, NSString * _Nonnull group2, YapDatabaseReadTransaction * _Nonnull transaction) { |
142 |
return [group1 compare:group2]; |
143 |
}]; |
144 |
|
145 |
|
146 |
} |
147 |
|
148 |
- (void)viewWillAppear:(BOOL)animated |
149 |
{ |
150 |
[[NSNotificationCenter defaultCenter] addObserver:self |
151 |
selector:@selector(keyboardWillShow:) |
152 |
name:UIKeyboardWillShowNotification |
153 |
object:nil]; |
154 |
[[NSNotificationCenter defaultCenter] addObserver:self |
155 |
selector:@selector(keyboardWillHide:) |
156 |
name:UIKeyboardWillHideNotification |
157 |
object:nil]; |
158 |
} |
159 |
|
160 |
- (void)inboxArchiveControlValueChanged:(id)sender { |
161 |
if (![sender isKindOfClass:[UISegmentedControl class]]) { |
162 |
return; |
163 |
} |
164 |
UISegmentedControl *segment = sender; |
165 |
BOOL showArchived = NO; |
166 |
if (segment.selectedSegmentIndex == 0) { |
167 |
showArchived = NO; |
168 |
} else if (segment.selectedSegmentIndex == 1) { |
169 |
showArchived = YES; |
170 |
} |
171 |
[self updateInboxArchiveFilteringAndShowArchived:showArchived]; |
172 |
} |
173 |
|
174 |
- (YapDatabaseViewFiltering *)getFilteringBlock:(BOOL)showArchived { |
175 |
YapDatabaseViewFiltering *filtering = [YapDatabaseViewFiltering withObjectBlock:^BOOL(YapDatabaseReadTransaction * _Nonnull transaction, NSString * _Nonnull group, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) { |
176 |
if ([object conformsToProtocol:@protocol(OTRThreadOwner)]) { |
177 |
id<OTRThreadOwner> threadOwner = object; |
178 |
BOOL isArchived = threadOwner.isArchived; |
179 |
return showArchived == isArchived; |
180 |
} |
181 |
return YES; |
182 |
}]; |
183 |
return filtering; |
184 |
} |
185 |
|
186 |
- (void) updateInboxArchiveFilteringAndShowArchived:(BOOL)showArchived { |
187 |
[[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { |
188 |
YapDatabaseFilteredViewTransaction *fvt = [transaction ext:OTRArchiveFilteredBuddiesName]; |
189 |
YapDatabaseViewFiltering *filtering = [self getFilteringBlock:showArchived]; |
190 |
[fvt setFiltering:filtering versionTag:[NSUUID UUID].UUIDString]; |
191 |
}]; |
192 |
} |
193 |
|
194 |
- (void)viewWillDisappear:(BOOL)animated |
195 |
{ |
196 |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
197 |
} |
198 |
|
199 |
- (void)setupSearchController { |
200 |
UITableViewController *searchResultsController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain]; |
201 |
[searchResultsController.tableView registerClass:[OTRBuddyInfoCell class] forCellReuseIdentifier:[OTRBuddyInfoCell reuseIdentifier]]; |
202 |
|
203 |
searchResultsController.tableView.dataSource = self; |
204 |
searchResultsController.tableView.delegate = self; |
205 |
searchResultsController.tableView.estimatedRowHeight = 120; |
206 |
searchResultsController.tableView.rowHeight = UITableViewAutomaticDimension; |
207 |
|
208 |
self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsController]; |
209 |
self.searchController.searchResultsUpdater = self; |
210 |
self.searchController.dimsBackgroundDuringPresentation = NO; |
211 |
self.searchController.hidesNavigationBarDuringPresentation = NO; |
212 |
self.searchController.delegate = self; |
213 |
|
214 |
self.definesPresentationContext = YES; |
215 |
|
216 |
[self.searchController.searchBar sizeToFit]; |
217 |
//self.searchController.searchBar.placeholder = SEARCH_STRING; |
218 |
self.searchController.searchBar.searchBarStyle = UISearchBarStyleMinimal; |
219 |
self.searchController.searchBar.delegate = self; |
220 |
[self.tableViewHeader addStackedSubview:self.searchController.searchBar identifier:nil gravity:OTRVerticalStackViewGravityTop]; |
221 |
} |
222 |
|
223 |
// Make sure bar stays at the top |
224 |
- (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar { |
225 |
if ([bar isKindOfClass:UISearchBar.class]) { |
226 |
return UIBarPositionTopAttached; |
227 |
} |
228 |
return UIBarPositionTop; |
229 |
} |
230 |
|
231 |
- (BOOL)isSearchResultsControllerTableView:(UITableView *)tableView |
232 |
{ |
233 |
UITableViewController *src = (UITableViewController*)self.searchController.searchResultsController; |
234 |
if (tableView == src.tableView) { |
235 |
return YES; |
236 |
} |
237 |
return NO; |
238 |
} |
239 |
|
240 |
- (OTRYapViewHandler *)viewHandlerForTableView:(UITableView *)tableView { |
241 |
if ([self isSearchResultsControllerTableView:tableView]) { |
242 |
return self.searchViewHandler; |
243 |
} |
244 |
return self.viewHandler; |
245 |
} |
246 |
|
247 |
- (void)cancelButtonPressed:(id)sender |
248 |
{ |
249 |
// Call dismiss from the split view controller instead |
250 |
[self.delegate controllerDidCancel:self]; |
251 |
} |
252 |
|
253 |
- (void)doneButtonPressed:(id)sender |
254 |
{ |
255 |
__weak __typeof__(self) weakSelf = self; |
256 |
void (^completion)(NSString *) = ^void(NSString *name) { |
257 |
__typeof__(self) strongSelf = weakSelf; |
258 |
[strongSelf completeSelectingBuddies:strongSelf.selectedBuddiesIdSet groupName:name]; |
259 |
}; |
260 |
|
261 |
if (self.selectedBuddiesIdSet.count > 1) { |
262 |
//Group so need user to select name |
263 |
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:GROUP_NAME_STRING() message:ENTER_GROUP_NAME_STRING() preferredStyle:UIAlertControllerStyleAlert]; |
264 |
UIAlertAction *okAction = [UIAlertAction actionWithTitle:OK_STRING() style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { |
265 |
NSString *name = alertController.textFields.firstObject.text; |
266 |
completion(name); |
267 |
}]; |
268 |
|
269 |
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { |
270 |
|
271 |
}]; |
272 |
|
273 |
[alertController addAction:okAction]; |
274 |
[alertController addAction:cancelAction]; |
275 |
|
276 |
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { |
277 |
textField.placeholder = GROUP_NAME_STRING(); |
278 |
|
279 |
[[NSNotificationCenter defaultCenter] addObserverForName:UITextFieldTextDidChangeNotification object:textField queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { |
280 |
okAction.enabled = textField.text.length > 0; |
281 |
}]; |
282 |
}]; |
283 |
|
284 |
|
285 |
[self presentViewController:alertController animated:YES completion:nil]; |
286 |
} else { |
287 |
completion(nil); |
288 |
} |
289 |
} |
290 |
|
291 |
/** Intended to be called if selecting one buddy or after a group chat is created*/ |
292 |
- (void)completeSelectingBuddies:(NSSet <NSString *>*)buddies groupName:(nullable NSString*)groupName { |
293 |
if (![self.delegate respondsToSelector:@selector(controller:didSelectBuddies:accountId:name:)]) { |
294 |
return; |
295 |
} |
296 |
//TODO: Naive choosing account just any buddy but should really check that account is connected or show picker |
297 |
__block NSString *accountId = nil; |
298 |
[self.viewHandler.databaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { |
299 |
NSString *buddyKey = [buddies anyObject]; |
300 |
accountId = [OTRBuddy fetchObjectWithUniqueID:buddyKey transaction:transaction].accountUniqueId; |
301 |
}]; |
302 |
if (!accountId) { |
303 |
DDLogError(@"completeSelectingBuddies error: No account found!"); |
304 |
return; |
305 |
} |
306 |
[self.delegate controller:self didSelectBuddies:[buddies allObjects] accountId:accountId name:groupName]; |
307 |
} |
308 |
|
309 |
- (void) groupButtonPressed:(id)sender { |
310 |
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"OTRComposeGroup" bundle:[OTRAssets resourcesBundle]]; |
311 |
OTRComposeGroupViewController *vc = (OTRComposeGroupViewController *)[storyboard instantiateInitialViewController]; |
312 |
vc.delegate = self; |
313 |
[self.navigationController pushViewController:vc animated:YES]; |
314 |
} |
315 |
|
316 |
- (void)switchSelectionMode { |
317 |
_selectionModeIsSingle = !_selectionModeIsSingle; |
318 |
|
319 |
// Change from Join Group / Add Buddy |
320 |
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0]; |
321 |
[self.tableView reloadRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationNone]; |
322 |
|
323 |
//Update right bar button item |
324 |
if (self.selectionModeIsSingle) { |
325 |
self.navigationItem.rightBarButtonItem = self.groupBarButtonItem; |
326 |
} else { |
327 |
self.navigationItem.rightBarButtonItem = self.doneBarButtonItem; |
328 |
self.navigationItem.rightBarButtonItem.enabled = NO; |
329 |
} |
330 |
|
331 |
|
332 |
} |
333 |
|
334 |
- (id<OTRThreadOwner>)threadOwnerAtIndexPath:(NSIndexPath *)indexPath withTableView:(UITableView *)tableView |
335 |
{ |
336 |
NSIndexPath *viewIndexPath = [NSIndexPath indexPathForItem:indexPath.row inSection:0]; |
337 |
OTRYapViewHandler *viewHandler = [self viewHandlerForTableView:tableView]; |
338 |
return [viewHandler object:viewIndexPath]; |
339 |
} |
340 |
|
341 |
- (void)selectedThreadOwner:(NSString *)buddyId{ |
342 |
if (![buddyId length]) { |
343 |
return; |
344 |
} |
345 |
|
346 |
if ([self.selectedBuddiesIdSet containsObject:buddyId]) { |
347 |
[self.selectedBuddiesIdSet removeObject:buddyId]; |
348 |
} else { |
349 |
[self.selectedBuddiesIdSet addObject:buddyId]; |
350 |
} |
351 |
|
352 |
//Check legth of selected buddies and if none then disable button |
353 |
if ([self.selectedBuddiesIdSet count]) { |
354 |
self.navigationItem.rightBarButtonItem.enabled = YES; |
355 |
} else { |
356 |
[self switchSelectionMode]; |
357 |
} |
358 |
} |
359 |
#pragma - mark keyBoardAnimation Methods |
360 |
- (void)keyboardWillShow:(NSNotification *)notification |
361 |
{ |
362 |
[self animateTableViewWithKeyboardNotification:notification]; |
363 |
} |
364 |
- (void)keyboardWillHide:(NSNotification *)notification |
365 |
{ |
366 |
[self animateTableViewWithKeyboardNotification:notification]; |
367 |
} |
368 |
|
369 |
- (void)animateTableViewWithKeyboardNotification:(NSNotification *)notification |
370 |
{ |
371 |
NSDictionary *userInfo = notification.userInfo; |
372 |
|
373 |
// |
374 |
// Get keyboard size. |
375 |
NSValue *endFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey]; |
376 |
CGRect keyboardEndFrame = [self.view convertRect:endFrameValue.CGRectValue fromView:nil]; |
377 |
|
378 |
// |
379 |
// Get keyboard animation. |
380 |
NSNumber *durationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey]; |
381 |
NSTimeInterval animationDuration = durationValue.doubleValue; |
382 |
|
383 |
NSNumber *curveValue = userInfo[UIKeyboardAnimationCurveUserInfoKey]; |
384 |
UIViewAnimationCurve animationCurve = curveValue.intValue; |
385 |
|
386 |
CGFloat viewHeight = CGRectGetMaxY(self.view.frame); |
387 |
CGFloat keyboardY = CGRectGetMinY(keyboardEndFrame); |
388 |
|
389 |
CGFloat height = viewHeight - keyboardY; |
390 |
//If height is less than 0 or it's hiding set to 0 |
391 |
if ([notification.name isEqualToString:UIKeyboardWillHideNotification] || height < 0) { |
392 |
height = 0; |
393 |
} |
394 |
|
395 |
[self animateTableViewToKeyboardHeight:height animationCurve:animationCurve animationDuration:animationDuration]; |
396 |
} |
397 |
|
398 |
- (void)animateTableViewToKeyboardHeight:(CGFloat)keyBoardHeight animationCurve:(UIViewAnimationCurve)animationCurve animationDuration:(NSTimeInterval)animationDuration |
399 |
{ |
400 |
self.tableViewBottomConstraint.constant = -keyBoardHeight; |
401 |
void (^animations)() = ^() { |
402 |
[self.view layoutIfNeeded]; |
403 |
}; |
404 |
[UIView animateWithDuration:animationDuration |
405 |
delay:0.0 |
406 |
options:(animationCurve << 16) |
407 |
animations:animations |
408 |
completion:nil]; |
409 |
|
410 |
} |
411 |
|
412 |
#pragma - mark UITableViewDataSource Methods |
413 |
|
414 |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView |
415 |
{ |
416 |
OTRYapViewHandler *viewHandler = [self viewHandlerForTableView:tableView]; |
417 |
return [viewHandler.mappings numberOfSections]; |
418 |
} |
419 |
|
420 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section |
421 |
{ |
422 |
OTRYapViewHandler *viewHandler = [self viewHandlerForTableView:tableView]; |
423 |
return [viewHandler.mappings numberOfItemsInSection:section]; |
424 |
} |
425 |
|
426 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath |
427 |
{ |
428 |
OTRBuddyInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:[OTRBuddyInfoCell reuseIdentifier] forIndexPath:indexPath]; |
429 |
id<OTRThreadOwner> threadOwner = [self threadOwnerAtIndexPath:indexPath withTableView:tableView]; |
430 |
|
431 |
__block OTRAccount *account = nil; |
432 |
[OTRDatabaseManager.shared.readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { |
433 |
BOOL showAccount = [self shouldShowAccountLabelWithTransaction:transaction]; |
434 |
if (showAccount) { |
435 |
account = [OTRAccount accountForThread:threadOwner transaction:transaction]; |
436 |
} |
437 |
}]; |
438 |
|
439 |
[cell setThread:threadOwner account:account]; |
440 |
|
441 |
if ([self.selectedBuddiesIdSet containsObject:[threadOwner threadIdentifier]]) { |
442 |
cell.accessoryType = UITableViewCellAccessoryCheckmark; |
443 |
} else { |
444 |
cell.accessoryType = UITableViewCellAccessoryNone; |
445 |
} |
446 |
return cell; |
447 |
} |
448 |
|
449 |
/** By default will show account if numAccounts > 0*/ |
450 |
- (BOOL) shouldShowAccountLabelWithTransaction:(YapDatabaseReadTransaction*)transaction { |
451 |
NSUInteger numberOfAccounts = 0; |
452 |
numberOfAccounts = [OTRAccount numberOfAccountsWithTransaction:transaction]; |
453 |
if (numberOfAccounts > 1) { |
454 |
return YES; |
455 |
} |
456 |
return NO; |
457 |
} |
458 |
|
459 |
#pragma - mark UITableViewDelegate Methods |
460 |
|
461 |
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath |
462 |
{ |
463 |
return OTRBuddyInfoCellHeight; |
464 |
} |
465 |
|
466 |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath |
467 |
{ |
468 |
return OTRBuddyInfoCellHeight; |
469 |
} |
470 |
|
471 |
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath |
472 |
{ |
473 |
return UITableViewCellEditingStyleDelete; |
474 |
} |
475 |
|
476 |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath |
477 |
{ |
478 |
[tableView deselectRowAtIndexPath:indexPath animated:YES]; |
479 |
id<OTRThreadOwner> threadOwner = [self threadOwnerAtIndexPath:indexPath withTableView:tableView]; |
480 |
if (self.selectionModeIsSingle == YES) { |
481 |
NSSet <NSString *>*buddySet = [NSSet setWithObject:[threadOwner threadIdentifier]]; |
482 |
[self completeSelectingBuddies:buddySet groupName:nil]; |
483 |
} else { |
484 |
[self selectedThreadOwner:[threadOwner threadIdentifier]]; |
485 |
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; |
486 |
} |
487 |
} |
488 |
|
489 |
- (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { |
490 |
NSIndexPath *databaseIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0]; |
491 |
id <OTRThreadOwner> thread = [self threadOwnerAtIndexPath:databaseIndexPath withTableView:tableView]; |
492 |
if (!thread) { return nil; } |
493 |
return [UITableView editActionsForThread:thread deleteActionAlsoRemovesFromRoster:YES connection:OTRDatabaseManager.shared.readWriteDatabaseConnection]; |
494 |
} |
495 |
|
496 |
- (void)addBuddy:(NSArray *)accountsAbleToAddBuddies |
497 |
{ |
498 |
if ([accountsAbleToAddBuddies count] == 0) { |
499 |
return; // No accounts |
500 |
} |
501 |
|
502 |
//add buddy cell |
503 |
UIViewController *viewController = nil; |
504 |
if([accountsAbleToAddBuddies count] > 1) { |
505 |
// pick which account |
506 |
OTRChooseAccountViewController *chooser = [[OTRChooseAccountViewController alloc] init]; |
507 |
chooser.selectionBlock = ^(OTRChooseAccountViewController * _Nonnull chooseVC, OTRAccount * _Nonnull account) { |
508 |
OTRNewBuddyViewController *newBuddyVC = [[OTRNewBuddyViewController alloc] initWithAccountId:account.uniqueId]; |
509 |
[chooseVC.navigationController pushViewController:newBuddyVC animated:YES]; |
510 |
}; |
511 |
viewController = chooser; |
512 |
} |
513 |
else { |
514 |
OTRAccount *account = [accountsAbleToAddBuddies firstObject]; |
515 |
viewController = [[OTRNewBuddyViewController alloc] initWithAccountId:account.uniqueId]; |
516 |
} |
517 |
[self.navigationController pushViewController:viewController animated:YES]; |
518 |
} |
519 |
|
520 |
- (void) joinGroup:(id)sender { |
521 |
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:JOIN_GROUP_STRING() message:nil preferredStyle:UIAlertControllerStyleAlert]; |
522 |
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { |
523 |
textField.placeholder = @"room@conference.example.com"; |
524 |
}]; |
525 |
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { |
526 |
textField.placeholder = [NSString stringWithFormat:@"%@ (%@)", PASSWORD_STRING(), OPTIONAL_STRING()]; |
527 |
}]; |
528 |
UIAlertAction *joinAction = [UIAlertAction actionWithTitle:OK_STRING() style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { |
529 |
NSString *jidStr = alertController.textFields.firstObject.text; |
530 |
NSString *pass = alertController.textFields.lastObject.text; |
531 |
if (!jidStr.length) { |
532 |
return; |
533 |
} |
534 |
XMPPJID *roomJid = [XMPPJID jidWithString:jidStr]; |
535 |
if (!roomJid) { |
536 |
return; |
537 |
} |
538 |
NSArray *accounts = [OTRAccountsManager allAccounts]; |
539 |
void (^joinRoom)(OTRAccount *account) = ^void(OTRAccount *account) { |
540 |
OTRXMPPManager *xmpp = (OTRXMPPManager*)[OTRProtocolManager.shared protocolForAccount:account]; |
541 |
if (!xmpp) { return; } |
542 |
[xmpp.roomManager joinRoom:roomJid withNickname:account.displayName subject:nil password:pass]; |
543 |
[self dismissViewControllerAnimated:YES completion:nil]; |
544 |
}; |
545 |
if (accounts.count > 1) { |
546 |
OTRChooseAccountViewController *chooser = [[OTRChooseAccountViewController alloc] init]; |
547 |
chooser.selectionBlock = ^(OTRChooseAccountViewController * _Nonnull chooseVC, OTRAccount * _Nonnull account) { |
548 |
[chooseVC dismissViewControllerAnimated:YES completion:nil]; |
549 |
joinRoom(account); |
550 |
}; |
551 |
[self.navigationController pushViewController:chooser animated:YES]; |
552 |
} else { |
553 |
joinRoom(accounts.firstObject); |
554 |
} |
555 |
}]; |
556 |
UIAlertAction *cancel = [UIAlertAction actionWithTitle:CANCEL_STRING() style:UIAlertActionStyleCancel handler:nil]; |
557 |
[alertController addAction:joinAction]; |
558 |
[alertController addAction:cancel]; |
559 |
[self presentViewController:alertController animated:YES completion:nil]; |
560 |
} |
561 |
|
562 |
#pragma - mark UIScrollViewDelegate |
563 |
|
564 |
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView |
565 |
{ |
566 |
[self.view endEditing:YES]; |
567 |
} |
568 |
|
569 |
#pragma - mark YapDatabaseViewUpdate |
570 |
|
571 |
- (void)didSetupMappings:(OTRYapViewHandler *)handler |
572 |
{ |
573 |
UITableView *tableView = nil; |
574 |
if (self.searchViewHandler == handler) { |
575 |
tableView = ((UITableViewController*)self.searchController.searchResultsController).tableView; |
576 |
} else if (self.viewHandler == handler) { |
577 |
tableView = self.tableView; |
578 |
} |
579 |
[tableView reloadData]; |
580 |
} |
581 |
|
582 |
- (void)didReceiveChanges:(OTRYapViewHandler *)handler sectionChanges:(NSArray<YapDatabaseViewSectionChange *> *)sectionChanges rowChanges:(NSArray<YapDatabaseViewRowChange *> *)rowChanges |
583 |
{ |
584 |
if ([sectionChanges count] == 0 && [rowChanges count] == 0) |
585 |
{ |
586 |
// Nothing has changed that affects our tableView |
587 |
return; |
588 |
} |
589 |
|
590 |
UITableView *tableView = self.tableView; |
591 |
BOOL isSearchViewHandler = NO; |
592 |
if (self.searchViewHandler == handler) { |
593 |
isSearchViewHandler = YES; |
594 |
tableView = ((UITableViewController*)self.searchController.searchResultsController).tableView; |
595 |
} |
596 |
|
597 |
[tableView beginUpdates]; |
598 |
|
599 |
for (YapDatabaseViewSectionChange *sectionChange in sectionChanges) |
600 |
{ |
601 |
switch (sectionChange.type) |
602 |
{ |
603 |
case YapDatabaseViewChangeDelete : |
604 |
{ |
605 |
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionChange.index] |
606 |
withRowAnimation:UITableViewRowAnimationAutomatic]; |
607 |
break; |
608 |
} |
609 |
case YapDatabaseViewChangeInsert : |
610 |
{ |
611 |
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionChange.index] |
612 |
withRowAnimation:UITableViewRowAnimationAutomatic]; |
613 |
break; |
614 |
} |
615 |
case YapDatabaseViewChangeUpdate: |
616 |
case YapDatabaseViewChangeMove: |
617 |
break; |
618 |
} |
619 |
} |
620 |
|
621 |
for (YapDatabaseViewRowChange *rowChange in rowChanges) |
622 |
{ |
623 |
switch (rowChange.type) |
624 |
{ |
625 |
case YapDatabaseViewChangeDelete : |
626 |
{ |
627 |
[tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ] |
628 |
withRowAnimation:UITableViewRowAnimationAutomatic]; |
629 |
break; |
630 |
} |
631 |
case YapDatabaseViewChangeInsert : |
632 |
{ |
633 |
[tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ] |
634 |
withRowAnimation:UITableViewRowAnimationAutomatic]; |
635 |
break; |
636 |
} |
637 |
case YapDatabaseViewChangeMove : |
638 |
{ |
639 |
[tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ] |
640 |
withRowAnimation:UITableViewRowAnimationAutomatic]; |
641 |
[tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ] |
642 |
withRowAnimation:UITableViewRowAnimationAutomatic]; |
643 |
break; |
644 |
} |
645 |
case YapDatabaseViewChangeUpdate : |
646 |
{ |
647 |
[tableView reloadRowsAtIndexPaths:@[ rowChange.indexPath ] |
648 |
withRowAnimation:UITableViewRowAnimationNone]; |
649 |
break; |
650 |
} |
651 |
} |
652 |
} |
653 |
|
654 |
[tableView endUpdates]; |
655 |
} |
656 |
|
657 |
#pragma - mark UISearchResultsUpdating |
658 |
|
659 |
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController { |
660 |
NSString *searchString = [self.searchController.searchBar text]; |
661 |
if ([searchString length]) { |
662 |
searchString = [NSString stringWithFormat:@"%@*",searchString]; |
663 |
[self.searchQueue enqueueQuery:searchString]; |
664 |
[self.searchConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { |
665 |
NSString *searchViewName = [YapDatabaseConstants extensionName:DatabaseExtensionNameBuddySearchResultsViewName]; |
666 |
[[transaction ext:searchViewName] performSearchWithQueue:self.searchQueue]; |
667 |
}]; |
668 |
} |
669 |
} |
670 |
|
671 |
#pragma - mark UISearchControllerDelegate |
672 |
|
673 |
- (void)willDismissSearchController:(UISearchController *)searchController |
674 |
{ |
675 |
//Make sure all the checkmarks are correctly updated |
676 |
[self.tableView reloadData]; |
677 |
} |
678 |
|
679 |
#pragma - mark OTRComposeGroupViewControllerDelegate |
680 |
|
681 |
- (void)groupBuddiesSelected:(OTRComposeGroupViewController *)composeViewController buddyUniqueIds:(NSArray<NSString *> *)buddyUniqueIds groupName:(NSString *)groupName { |
682 |
[self completeSelectingBuddies:[NSSet setWithArray:buddyUniqueIds] groupName:groupName]; |
683 |
} |
684 |
|
685 |
- (void)groupSelectionCancelled:(OTRComposeGroupViewController *)composeViewController { |
686 |
} |
687 |
|
688 |
@end |