1//
2//  SWCViewController.m
3//  SharedWebCredentialViewService
4//
5//  Copyright (c) 2014 Apple Inc. All Rights Reserved.
6//
7
8#import "SWCViewController.h"
9#import <UIKit/UIViewController_Private.h>
10#import <UIKit/UIFont_Private.h>
11
12#import <UIKit/UIAlertController_Private.h>
13#import <UIKit/UITableViewCell_Private.h>
14
15#include <bsm/libbsm.h>
16#include <ipc/securityd_client.h>
17#include "SharedWebCredential/swcagent_client.h"
18
19const NSString* SWC_PASSWORD_KEY = @"spwd";
20const NSString* SWC_ACCOUNT_KEY  = @"acct";
21const NSString* SWC_SERVER_KEY   = @"srvr";
22
23//
24// SWCDictionaryAdditions
25//
26
27@interface NSDictionary (SWCDictionaryAdditions)
28- (NSComparisonResult) compareCredentialDictionaryAscending:(NSDictionary *)other;
29@end
30
31@implementation NSDictionary (SWCDictionaryAdditions)
32- (NSComparisonResult)compareCredentialDictionaryAscending:(NSDictionary *)other
33{
34    NSComparisonResult result;
35    NSString *str1 = [self objectForKey:SWC_ACCOUNT_KEY], *str2 = [other objectForKey:SWC_ACCOUNT_KEY];
36    if (!str1) str1 = @"";
37    if (!str2) str2 = @"";
38    
39    // primary sort by account name
40    result = [str1 localizedCaseInsensitiveCompare:str2];
41    if (result == NSOrderedSame) {
42        // secondary sort by domain name
43        NSString *str3 = [self objectForKey:SWC_SERVER_KEY], *str4 = [other objectForKey:SWC_SERVER_KEY];
44        if (!str3) str3 = @"";
45        if (!str4) str4 = @"";
46        
47        result = [str3 localizedCaseInsensitiveCompare:str4];
48    }
49    
50    return result;
51}
52@end
53
54
55//
56// SWCItemCell
57//
58@interface SWCItemCell : UITableViewCell
59{
60    NSDictionary *_dict;
61    BOOL _isTicked;
62    UIView *_bottomLine;
63    UIView *_bottomLineSelected;
64    UIView *_topLine;
65    UIView *_topLineSelected;
66    BOOL _showSeparator;
67    BOOL _showTopSeparator;
68}
69
70- (id)initWithDictionary:(NSDictionary *)dict;
71@property (nonatomic, readonly) id userInfo;
72@property (nonatomic, assign) BOOL showSeparator;
73@end
74
75@implementation SWCItemCell
76
77@synthesize showSeparator = _showSeparator;
78
79- (id)initWithDictionary:(NSDictionary *)dict
80{
81    if ((self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]))
82    {
83        _dict = dict;
84        
85        self.selectionStyle = UITableViewCellSelectionStyleNone;
86        
87        self.backgroundColor = [UIColor colorWithWhite:0.1 alpha:0.005];
88        
89        self.textLabel.textColor = [UIColor blackColor];
90        self.textLabel.textAlignment = NSTextAlignmentLeft;
91        self.textLabel.AdjustsFontSizeToFitWidth = YES;
92        self.textLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
93        
94        NSString *title = [dict objectForKey:SWC_ACCOUNT_KEY];
95        self.textLabel.text = title ? title : @"--";
96        
97        self.detailTextLabel.textColor = [UIColor darkGrayColor];
98        self.detailTextLabel.textAlignment = NSTextAlignmentLeft;
99        self.detailTextLabel.AdjustsFontSizeToFitWidth = YES;
100        self.detailTextLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
101        
102        NSString *subtitle = [dict objectForKey:SWC_SERVER_KEY];
103        self.detailTextLabel.text = subtitle ? subtitle : @"--";
104        
105        self.backgroundView = [[UIView alloc] init];
106        self.backgroundView.backgroundColor = self.backgroundColor;
107        
108        self.imageView.image = [self _checkmarkImage: NO];
109        self.imageView.hidden = YES;
110        
111    }
112    
113    return self;
114}
115
116- (void)setTicked: (BOOL) selected
117{
118    _isTicked = selected;
119}
120
121- (void)layoutSubviews
122{
123    
124    if (_bottomLine) {
125        CGFloat scale = [[UIScreen mainScreen] scale];
126        [_bottomLine setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)];
127    }
128    
129    if (_bottomLineSelected) {
130        CGFloat scale = [[UIScreen mainScreen] scale];
131        [_bottomLineSelected setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)];
132    }
133    
134    if (_topLine) {
135        CGFloat scale = [[UIScreen mainScreen] scale];
136        [_topLine setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)];
137    }
138    
139    if (_topLineSelected) {
140        CGFloat scale = [[UIScreen mainScreen] scale];
141        [_topLineSelected setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)];
142    }
143    
144    if (_isTicked)
145    {
146        self.imageView.hidden = NO;
147    } else {
148        self.imageView.hidden = YES;
149    }
150    
151    [super layoutSubviews];
152    
153}
154
155- (void)setShowSeparator:(BOOL)showSeparator {
156    if (_showSeparator != showSeparator) {
157        _showSeparator = showSeparator;
158        
159        if (_showSeparator) {
160            if (!_bottomLine) {
161                CGRect rectZero = CGRectMake(0, 0, 0, 0);
162                _bottomLine = [[UIView alloc] initWithFrame:rectZero];
163                _bottomLine.backgroundColor = [UIColor colorWithWhite:.5 alpha:.5];
164                [self.backgroundView addSubview:_bottomLine];
165            }
166            if (!_bottomLineSelected) {
167                CGRect rectZero = CGRectMake(0, 0, 0, 0);
168                _bottomLineSelected = [[UIView alloc] initWithFrame:rectZero];
169                _bottomLineSelected.backgroundColor = [UIColor colorWithWhite:.5 alpha:.5];
170                [self.selectedBackgroundView addSubview: _bottomLineSelected];
171            }
172            
173        } else {
174            if (_bottomLine) {
175                [_bottomLine removeFromSuperview];
176                _bottomLine = nil;
177            }
178            if (_bottomLineSelected) {
179                [_bottomLineSelected removeFromSuperview];
180                _bottomLineSelected = nil;
181            }
182        }
183    }
184}
185
186- (void)setShowTopSeparator:(BOOL)showTopSeparator {
187    if (_showTopSeparator != showTopSeparator) {
188        _showTopSeparator = showTopSeparator;
189        
190        if (_showTopSeparator) {
191            if (!_topLine) {
192                CGRect rectZero = CGRectMake(0, 0, 0, 0);
193                _topLine = [[UIView alloc] initWithFrame:rectZero];
194                _topLine.backgroundColor = [UIColor colorWithWhite:.5 alpha:.5];
195                [self.backgroundView addSubview:_topLine];
196            }
197            if (!_topLineSelected) {
198                CGRect rectZero = CGRectMake(0, 0, 0, 0);
199                _topLineSelected = [[UIView alloc] initWithFrame:rectZero];
200                _topLineSelected.backgroundColor = [UIColor colorWithWhite:.5 alpha:.5];
201                [self.selectedBackgroundView addSubview: _topLineSelected];
202            }
203            
204        } else {
205            if (_topLine) {
206                [_topLine removeFromSuperview];
207                _topLine = nil;
208            }
209            if (_topLineSelected) {
210                [_topLineSelected removeFromSuperview];
211                _topLineSelected = nil;
212            }
213        }
214    }
215}
216
217@end
218
219
220//
221// SWCViewController
222//
223
224@interface SWCViewController ()
225{
226    NSMutableArray       *_credentials; // array of NSDictionary
227    UILabel              *_topLabel;
228    UILabel              *_middleLabel;
229    UITableView          *_table;
230    NSDictionary         *_selectedDict;
231    NSIndexPath          *_selectedCell;
232}
233
234@end
235
236@implementation SWCViewController
237
238- (NSDictionary *)selectedItem
239{
240    return _selectedDict;
241}
242
243- (void)setCredentials:(NSArray *)inArray
244{
245    NSMutableArray *credentials = [[NSMutableArray alloc] initWithArray:inArray];
246    [credentials sortUsingSelector:@selector(compareCredentialDictionaryAscending:)];
247    _credentials = credentials;
248    if (_table)
249        [_table reloadData];
250}
251
252- (void)_enableTable
253{
254    [_table setUserInteractionEnabled:YES];
255}
256
257- (UITableView *)tableView
258{
259    if (_table == nil) {
260        _table = [[UITableView alloc] init];
261        [_table setTranslatesAutoresizingMaskIntoConstraints:NO];
262        [_table setAutoresizingMask:UIViewAutoresizingNone];
263        [_table setBackgroundColor:[UIColor clearColor]];
264        [_table setSeparatorStyle:UITableViewCellSeparatorStyleNone];
265    }
266    [_table sizeToFit];
267    
268    return (UITableView *)_table;
269}
270
271-(void)loadView
272{
273    
274    UIView* view = [[UIView alloc] init];
275    
276    UITableView* table = [self tableView];
277    [table setDelegate: self];
278    [table setDataSource: self];
279    
280    [view addSubview: table];
281    
282    CFErrorRef error = NULL;
283    audit_token_t auditToken = {};
284    memset(&auditToken, 0, sizeof(auditToken));
285    CFArrayRef credentialList = swca_copy_pairs(swca_copy_pairs_request_id, &auditToken, &error);
286    if (error) {
287        NSLog(@"Unable to get accounts: %@", [(__bridge NSError*)error localizedDescription]);
288    }
289    
290    [self setCredentials:(__bridge NSArray*)credentialList];
291    if (credentialList) {
292        CFRelease(credentialList);
293    }
294    
295    
296    NSDictionary* views = NSDictionaryOfVariableBindings(table);
297    
298    if ([_credentials count] > 2)
299    {
300        
301        NSDictionary *metrics = @{@"height":@120.0};
302        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[table]-|"  options:0 metrics:metrics views:views]];
303        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]];
304        
305        CGFloat scale = [[UIScreen mainScreen] scale];
306        table.layer.borderWidth = 1.0 / scale;
307        table.layer.borderColor = [UIColor colorWithWhite:.5 alpha:.5].CGColor;
308        
309        [self setPreferredContentSize:CGSizeMake(0,140)];
310        
311    } else if ([_credentials count] == 2) {
312        
313        NSDictionary *metrics = @{@"height":@90.0};
314        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|"  options:0 metrics:metrics views:views]];
315        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]];
316        
317        [self setPreferredContentSize:CGSizeMake(0,90)];
318        [table setScrollEnabled: NO];
319        
320    } else {  // [_credentials count] == 1
321        
322        NSDictionary *metrics = @{@"height":@45.0};
323        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|"  options:0 metrics:metrics views:views]];
324        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]|" options:0 metrics:metrics views:views]];
325        
326        [self setPreferredContentSize:CGSizeMake(0,45)];
327        [table setScrollEnabled: NO];
328        
329    }
330    
331    [self setView:view];
332}
333
334-(void)viewWillAppear:(BOOL)animated
335{
336    
337    // Select the first cell by default
338    
339    NSDictionary *dict = [_credentials objectAtIndex: 0];
340    _selectedDict = dict;
341    _selectedCell = [NSIndexPath indexPathForItem:0 inSection: 0];
342    SWCItemCell *cell = (SWCItemCell *)[_table cellForRowAtIndexPath: _selectedCell];
343    [cell setTicked: YES];
344    [_table selectRowAtIndexPath: _selectedCell animated: NO scrollPosition: UITableViewScrollPositionTop];
345    
346    CFErrorRef error = NULL;
347    audit_token_t auditToken = {};
348    memset(&auditToken, 0, sizeof(auditToken));
349    bool result = swca_set_selection(swca_set_selection_request_id,
350                                     &auditToken, (__bridge CFDictionaryRef)dict, &error);
351    if (!result) {
352        NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]);
353    }
354    
355    [super viewWillAppear:animated];
356}
357
358
359//
360// UITableView delegate methods
361//
362
363- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
364{
365    return [_credentials count];
366}
367
368- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
369{
370    NSUInteger row = indexPath.row;
371    
372    NSDictionary *dict = [_credentials objectAtIndex:row];
373    _selectedDict = dict;
374    
375    CFErrorRef error = NULL;
376    audit_token_t auditToken = {};
377    memset(&auditToken, 0, sizeof(auditToken));
378    bool result = swca_set_selection(swca_set_selection_request_id,
379                                     &auditToken, (__bridge CFDictionaryRef)dict, &error);
380    if (!result) {
381        NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]);
382    }
383    
384    _selectedCell = indexPath;
385    SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath];
386    [cell setTicked: YES];
387    [cell layoutSubviews];
388}
389
390- (void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
391{
392    SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath];
393    [cell setTicked: NO];
394    [cell layoutSubviews];
395    
396}
397
398- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
399{
400    
401    NSDictionary *dict = [_credentials objectAtIndex:[indexPath row]];
402    SWCItemCell *cell = [[SWCItemCell alloc] initWithDictionary:dict];
403    
404    // show separator on top cell if there's only or or two items
405    if ([_credentials count] <= 2) {
406        cell.showTopSeparator = YES;
407    } else {
408        cell.showSeparator = YES;
409        
410        if (indexPath.row == 0)
411        {
412            cell.showTopSeparator = YES;
413        }
414        
415    }
416    
417    if (_selectedCell == indexPath)
418    {
419        [cell setTicked: YES];
420    } else {
421        [cell setTicked: NO];
422    }
423    
424    return cell;
425}
426
427
428@end
429