1//
2//  KBUsersAppView.m
3//  Keybase
4//
5//  Created by Gabriel on 2/6/15.
6//  Copyright (c) 2015 Gabriel Handford. All rights reserved.
7//
8
9#import "KBUsersAppView.h"
10
11#import "KBUserProfileView.h"
12#import "KBProgressOverlayView.h"
13#import "KBUserView.h"
14#import "KBSearchField.h"
15#import "KBViews.h"
16#import "KBUserListView.h"
17#import "KBSearcher.h"
18#import "KBNotifications.h"
19
20#import <MDPSplitView/MDPSplitView.h>
21#import <YOLayout/YOLayout+PrefabLayouts.h>
22
23@interface KBUsersAppView ()
24@property MDPSplitView *splitView;
25@property YOView *leftView;
26@property YOView *rightView;
27
28@property KBSearchControl *searchField;
29@property NSPopUpButton *menuButton;
30
31@property KBUserListView *trackingView;
32@property KBUserListView *trackersView;
33@property KBUserListView *searchView;
34@property KBViews *views;
35
36@property KBUserProfileViewer *trackingUserView;
37@property KBUserProfileViewer *trackersUserView;
38@property KBUserProfileViewer *searchUserView;
39@property KBViews *userViews;
40@property KBActivityIndicatorView *listProgressView;
41
42@property KBActivityIndicatorView *searchProgressView;
43@property NSString *searchText;
44@property KBSearcher *search;
45@end
46
47@implementation KBUsersAppView
48
49- (void)viewInit {
50  [super viewInit];
51
52  YOSelf yself = self;
53
54  _splitView = [[MDPSplitView alloc] init];
55  _splitView.vertical = YES;
56  _splitView.dividerStyle = NSSplitViewDividerStyleThin;
57
58  _leftView = [YOView view];
59  {
60    _searchField = [[KBSearchField alloc] init];
61    _searchField.delegate = self;
62    [_leftView addSubview:_searchField];
63
64    NSMenu *menu = [[NSMenu alloc] init];
65    [menu addItemWithTitle:@"" action:NULL keyEquivalent:@""];
66    NSMenuItem *item;
67    item = [menu addItemWithTitle:@"Tracking" action:@selector(showTracking:) keyEquivalent:@""];
68    item.target = self;
69    item = [menu addItemWithTitle:@"Trackers" action:@selector(showTrackers:) keyEquivalent:@""];
70    item.target = self;
71    _menuButton = [[NSPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 320, 23) pullsDown:YES];
72    [_menuButton.cell setArrowPosition:NSPopUpArrowAtBottom];
73    _menuButton.bordered = NO;
74    [_menuButton setTarget:self];
75    [_menuButton setMenu:menu];
76    [_leftView addSubview:_menuButton];
77
78    GHWeakSelf gself = self;
79    _trackingView = [[KBUserListView alloc] init];
80    _trackingView.identifier = @"Tracking";
81    _trackingView.listView.onSelect = ^(KBTableView *tableView, KBTableSelection *selection) {
82      KBRUserSummary *userSummary = selection.object;
83      [gself.trackingUserView setUsername:userSummary.username client:gself.client];
84    };
85
86    _trackersView = [[KBUserListView alloc] init];
87    _trackersView.identifier = @"Trackers";
88    _trackersView.listView.onSelect = ^(KBTableView *tableView, KBTableSelection *selection) {
89      KBRUserSummary *userSummary = selection.object;
90      [gself.trackersUserView setUsername:userSummary.username client:gself.client];
91    };
92
93    _views = [[KBViews alloc] init];
94    [_views setViews:@[_trackingView, _trackersView]];
95    [_leftView addSubview:_views];
96
97    _searchProgressView = [[KBActivityIndicatorView alloc] init];
98    _searchProgressView.lineWidth = 1.0;
99    [_leftView addSubview:_searchProgressView];
100
101    _listProgressView = [[KBActivityIndicatorView alloc] init];
102    _listProgressView.lineWidth = 1.0;
103    [_leftView addSubview:_listProgressView];
104
105    _searchView = [[KBUserListView alloc] init];
106    _searchView.listView.onSelect = ^(KBTableView *tableView, KBTableSelection *selection) {
107      KBRUserSummary *userSummary = selection.object;
108      [gself.searchUserView setUsername:userSummary.username client:gself.client];
109    };
110
111    _leftView.viewLayout = [YOLayout layoutWithLayoutBlock:^(id<YOLayout> layout, CGSize size) {
112
113      CGFloat col = size.width;
114      // If this y is too small, the search field focus will conflict with the window title bar drag
115      // and the search field will become really janky.
116      // This isn't an issue anymore after I added KBAppTitleView but it was such an annoying bug I am
117      // leaving this comment here.
118      CGFloat y = 10;
119
120      [layout setFrame:CGRectMake(col - 46, y + 4, 14, 14) view:yself.searchProgressView];
121
122      y += [layout setFrame:CGRectMake(10, y, col - 21, 22) view:yself.searchField].size.height + 9;
123
124      [layout setFrame:CGRectMake(0, y, col, size.height - y) view:yself.searchView];
125
126      [layout setFrame:CGRectMake(7, y + 5, 14, 14) view:yself.listProgressView];
127      y += [layout setFrame:CGRectMake(13, y, col - 21, 23) view:yself.menuButton].size.height + 4;
128
129      [layout setFrame:CGRectMake(0, y, col, size.height - y) view:yself.views];
130
131      return size;
132    }];
133  }
134  [_splitView addSubview:_leftView];
135
136  _rightView = [YOView view];
137  {
138    _userViews = [[KBViews alloc] init];
139    _trackingUserView = [[KBUserProfileViewer alloc] init];
140    _trackingUserView.identifier = @"Tracking";
141    _trackersUserView = [[KBUserProfileViewer alloc] init];
142    _trackersUserView.identifier = @"Trackers";
143    [_userViews setViews:@[_trackingUserView, _trackersUserView]];
144    [_rightView addSubview:_userViews];
145
146    _searchUserView = [[KBUserProfileViewer alloc] init];
147
148    _rightView.viewLayout = [YOLayout layoutWithLayoutBlock:^(id<YOLayout> layout, CGSize size) {
149      [layout setSize:size view:yself.userViews options:0];
150      [layout setSize:size view:yself.searchUserView options:0];
151      return size;
152    }];
153  }
154  [_splitView addSubview:_rightView];
155
156  [_splitView adjustSubviews];
157  dispatch_async(dispatch_get_main_queue(), ^{
158    [yself.splitView setPosition:240 ofDividerAtIndex:0 animated:NO];
159  });
160
161  self.viewLayout = [YOLayout fill:_splitView];
162  [self addSubview:_splitView];
163
164  [self showTracking:self];
165  [_menuButton selectItemAtIndex:0];
166  [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(update) name:KBTrackingListDidChangeNotification object:nil];
167}
168
169- (void)dealloc {
170  [NSNotificationCenter.defaultCenter removeObserver:self];
171}
172
173- (void)showTracking:(id)sender {
174  [_views showViewWithIdentifier:@"Tracking"];
175  [_userViews showViewWithIdentifier:@"Tracking"];
176  [_menuButton setTitle:@"Tracking"];
177  if (!_trackingView.listView.selectedObject) [_trackingUserView clear];
178}
179
180- (void)showTrackers:(id)sender {
181  [_views showViewWithIdentifier:@"Trackers"];
182  [_userViews showViewWithIdentifier:@"Trackers"];
183  [_menuButton setTitle:@"Trackers"];
184  if (!_trackersView.listView.selectedObject) [_trackersUserView clear];
185}
186
187- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
188  BOOL on = [[menuItem title] isEqualTo:_views.visibleIdentifier];
189  [menuItem setState:on ? NSOnState : NSOffState];
190  return YES;
191}
192
193- (void)viewDidAppear:(BOOL)animated {
194  [self reload:NO];
195}
196
197- (void)reload:(BOOL)update {
198  GHWeakSelf gself = self;
199
200  _listProgressView.animating = YES;
201  KBRUserRequest *trackingRequest = [[KBRUserRequest alloc] initWithClient:self.client];
202  // TODO: Protocol changed
203  /*
204  [trackingRequest listTrackingWithFilter:nil completion:^(NSError *error, NSArray *userSummaries) {
205    gself.listProgressView.animating = NO;
206    if (error) {
207      [KBActivity setError:error sender:self];
208      [gself.trackingView.listView removeAllObjects];
209      return;
210    }
211    [self setTracking:userSummaries update:update];
212  }];
213   */
214
215  [self trackersUsers:^(NSError *error, NSArray *userSummaries) {
216    gself.listProgressView.animating = NO;
217    if (error) {
218      [KBActivity setError:error sender:self];
219      [gself.trackersView.listView removeAllObjects];
220      return;
221    }
222    [self setTrackers:userSummaries update:update];
223  }];
224}
225
226- (void)trackersUsers:(void (^)(NSError *error, NSArray *userSummaries))completion {
227  KBRUserRequest *trackersRequest = [[KBRUserRequest alloc] initWithClient:self.client];
228  [trackersRequest listTrackersSelf:^(NSError *error, NSArray *trackers) {
229    if (error) {
230      completion(error, nil);
231      return;
232    }
233    NSArray *uids = [trackers map:^id(KBRTracker *t) { return t.tracker; }];
234    KBRUserRequest *trackersRequest = [[KBRUserRequest alloc] initWithClient:self.client];
235    [trackersRequest loadUncheckedUserSummariesWithUids:uids completion:^(NSError *error, NSArray *userSummaries) {
236      if (error) {
237        completion(error, nil);
238        return;
239      }
240      completion(nil, userSummaries);
241    }];
242  }];
243}
244
245- (void)update {
246  [self reload:YES];
247}
248
249- (void)setTracking:(NSArray *)userSummaries update:(BOOL)update {
250  [_trackingView setUserSummaries:userSummaries update:update];
251}
252
253- (void)setTrackers:(NSArray *)userSummaries update:(BOOL)update {
254  [_trackersView setUserSummaries:userSummaries update:update];
255}
256
257#pragma mark Search
258
259- (void)showSearch {
260  if (![_searchView superview]) {
261    [_leftView addSubview:_searchView positioned:NSWindowAbove relativeTo:_views];
262  }
263  if (![_searchUserView superview]) {
264    [_rightView addSubview:_searchUserView positioned:NSWindowAbove relativeTo:_userViews];
265  }
266}
267
268- (void)hideSearch {
269  [_searchView removeFromSuperview];
270  [_searchUserView removeFromSuperview];
271}
272
273- (void)searchControlShouldOpen:(KBSearchControl *)searchControl {
274  [self showSearch];
275  if (!_searchView.listView.selectedObject) {
276    [_searchUserView clear];
277  }
278}
279
280- (void)searchControlShouldClose:(KBSearchControl *)searchControl {
281  [self hideSearch];
282}
283
284- (void)searchControl:(KBSearchControl *)searchControl shouldDisplaySearchResults:(KBSearchResults *)searchResults {
285  NSInteger previousCount = [_searchView.listView rowCount];
286
287  NSSet *usernames = [NSSet setWithArray:[[_searchView.listView objectsWithoutHeaders] map:^(KBRUserSummary *us) { return us.username; }]];
288  NSArray *filtered = [searchResults.results reject:^BOOL(KBRUserSummary *us) { return [usernames containsObject:us.username]; }];
289  NSMutableArray *results = [filtered mutableCopy];
290  if (searchResults.header && [results count] > 0) [results insertObject:[KBTableViewHeader tableViewHeaderWithTitle:searchResults.header] atIndex:0];
291  // Datasource might have been altered without reload here (if previousCount == 0), so animation won't work in that case (see reloadDelay)
292  [_searchView.listView addObjects:results animation:previousCount > 0 ? NSTableViewAnimationSlideUp : NSTableViewAnimationEffectNone];
293  [self showSearch];
294}
295
296- (void)searchControlShouldClearSearchResults:(KBSearchControl *)searchControl {
297  //[_searchView.listView removeAllObjects];
298  [_searchView.listView.dataSource removeAllObjects];
299  [_search reloadDelay:_searchView.listView];
300}
301
302- (void)searchControl:(KBSearchControl *)searchControl progressEnabled:(BOOL)progressEnabled {
303  _searchProgressView.animating = progressEnabled;
304}
305
306- (void)searchControl:(KBSearchControl *)searchControl shouldSearchWithQuery:(NSString *)query delay:(BOOL)delay completion:(void (^)(NSError *error, KBSearchResults *searchResults))completion {
307  if (!_search) _search = [[KBSearcher alloc] init];
308  [_search search:query client:self.client remote:delay completion:completion];
309}
310
311@end
312