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