Statistics
| Branch: | Tag: | Revision:

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