SWCViewController.m   [plain text]

//
// SWCViewController.m
// SharedWebCredentialViewService
//
// Copyright (c) 2014 Apple Inc. All Rights Reserved.
//

#import <Foundation/NSXPCConnection.h>
#import "SWCViewController.h"
#import <UIKit/UIViewController_Private.h>
#import <UIKit/UIFont_Private.h>
#import <UIKit/UIImage_SymbolImagePrivate.h>
#import <UIKit/UIAlertController_Private.h>
#import <UIKit/UITableViewCell_Private.h>

#include <bsm/libbsm.h>
#include <ipc/securityd_client.h>

#include "SharedWebCredential/swcagent_client.h"

#import "SWCViewController.h"

const NSString* SWC_PASSWORD_KEY = @"spwd";
const NSString* SWC_ACCOUNT_KEY = @"acct";
const NSString* SWC_SERVER_KEY = @"srvr";

//
// SWCDictionaryAdditions
//

@interface NSDictionary (SWCDictionaryAdditions)
- (NSComparisonResult) compareCredentialDictionaryAscending:(NSDictionary *)other;
@end

@implementation NSDictionary (SWCDictionaryAdditions)
- (NSComparisonResult)compareCredentialDictionaryAscending:(NSDictionary *)other
{
NSComparisonResult result;
NSString *str1 = [self objectForKey:SWC_ACCOUNT_KEY], *str2 = [other objectForKey:SWC_ACCOUNT_KEY];
if (!str1) str1 = @"";
if (!str2) str2 = @"";

// primary sort by account name
result = [str1 localizedCaseInsensitiveCompare:str2];
if (result == NSOrderedSame) {
// secondary sort by domain name
NSString *str3 = [self objectForKey:SWC_SERVER_KEY], *str4 = [other objectForKey:SWC_SERVER_KEY];
if (!str3) str3 = @"";
if (!str4) str4 = @"";

result = [str3 localizedCaseInsensitiveCompare:str4];
}

return result;
}
@end

//
// SWCItemCell
//
@interface SWCItemCell : UITableViewCell
{
NSDictionary *_dict;
BOOL _isTicked;
UIView *_bottomLine;
UIView *_bottomLineSelected;
UIView *_topLine;
UIView *_topLineSelected;
BOOL _showSeparator;
BOOL _showTopSeparator;
}

- (id)initWithDictionary:(NSDictionary *)dict;
@property (nonatomic, readonly) id userInfo;
@property (nonatomic, assign) BOOL showSeparator;
@end

@implementation SWCItemCell

@synthesize showSeparator = _showSeparator;

- (id)initWithDictionary:(NSDictionary *)dict
{
if ((self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]))
{
_dict = dict;

self.selectionStyle = UITableViewCellSelectionStyleNone;

self.backgroundColor = [UIColor systemBackgroundColor];

self.textLabel.textColor = [UIColor labelColor];
self.textLabel.textAlignment = NSTextAlignmentLeft;
self.textLabel.adjustsFontSizeToFitWidth = YES;
self.textLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;

NSString *title = [dict objectForKey:SWC_ACCOUNT_KEY];
self.textLabel.text = title ? title : NSLocalizedString(@"--", nil);

self.detailTextLabel.textColor = [UIColor secondaryLabelColor];
self.detailTextLabel.textAlignment = NSTextAlignmentLeft;
self.detailTextLabel.adjustsFontSizeToFitWidth = YES;
self.detailTextLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;

NSString *subtitle = [dict objectForKey:SWC_SERVER_KEY];
self.detailTextLabel.text = subtitle ? subtitle : NSLocalizedString(@"--", nil);

self.backgroundView = [[UIView alloc] init];
self.backgroundView.backgroundColor = [UIColor systemBackgroundColor];

self.imageView.image = [UIImage symbolImageNamed:@"checkmark"];
self.imageView.hidden = YES;
}

return self;
}

- (void)setTicked: (BOOL) selected
{
_isTicked = selected;
}

- (void)layoutSubviews
{

if (_bottomLine) {
CGFloat scale = [[UIScreen mainScreen] scale];
[_bottomLine setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)];
}

if (_bottomLineSelected) {
CGFloat scale = [[UIScreen mainScreen] scale];
[_bottomLineSelected setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)];
}

if (_topLine) {
CGFloat scale = [[UIScreen mainScreen] scale];
[_topLine setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)];
}

if (_topLineSelected) {
CGFloat scale = [[UIScreen mainScreen] scale];
[_topLineSelected setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)];
}

if (_isTicked)
{
self.imageView.hidden = NO;
} else {
self.imageView.hidden = YES;
}

[super layoutSubviews];

}

- (void)setShowSeparator:(BOOL)showSeparator {
if (_showSeparator != showSeparator) {
_showSeparator = showSeparator;

if (_showSeparator) {
if (!_bottomLine) {
CGRect rectZero = CGRectMake(0, 0, 0, 0);
_bottomLine = [[UIView alloc] initWithFrame:rectZero];
_bottomLine.backgroundColor = [UIColor separatorColor];
[self.backgroundView addSubview:_bottomLine];
}
if (!_bottomLineSelected) {
CGRect rectZero = CGRectMake(0, 0, 0, 0);
_bottomLineSelected = [[UIView alloc] initWithFrame:rectZero];
_bottomLineSelected.backgroundColor = [UIColor separatorColor];
[self.selectedBackgroundView addSubview: _bottomLineSelected];
}

} else {
if (_bottomLine) {
[_bottomLine removeFromSuperview];
_bottomLine = nil;
}
if (_bottomLineSelected) {
[_bottomLineSelected removeFromSuperview];
_bottomLineSelected = nil;
}
}
}
}

- (void)setShowTopSeparator:(BOOL)showTopSeparator {
if (_showTopSeparator != showTopSeparator) {
_showTopSeparator = showTopSeparator;

if (_showTopSeparator) {
if (!_topLine) {
CGRect rectZero = CGRectMake(0, 0, 0, 0);
_topLine = [[UIView alloc] initWithFrame:rectZero];
_topLine.backgroundColor = [UIColor separatorColor];
[self.backgroundView addSubview:_topLine];
}
if (!_topLineSelected) {
CGRect rectZero = CGRectMake(0, 0, 0, 0);
_topLineSelected = [[UIView alloc] initWithFrame:rectZero];
_topLineSelected.backgroundColor = [UIColor separatorColor];
[self.selectedBackgroundView addSubview: _topLineSelected];
}

} else {
if (_topLine) {
[_topLine removeFromSuperview];
_topLine = nil;
}
if (_topLineSelected) {
[_topLineSelected removeFromSuperview];
_topLineSelected = nil;
}
}
}
}

@end

//
// SWCViewController
//

@interface SWCViewController ()
{
NSMutableArray *_credentials; // array of NSDictionary
UILabel *_topLabel;
UILabel *_middleLabel;
UITableView *_table;
NSDictionary *_selectedDict;
NSIndexPath *_selectedCell;
}

@end

@implementation SWCViewController

- (NSDictionary *)selectedItem
{
return _selectedDict;
}

- (void)setCredentials:(NSArray *)inArray
{
NSMutableArray *credentials = [[NSMutableArray alloc] initWithArray:inArray];
[credentials sortUsingSelector:@selector(compareCredentialDictionaryAscending:)];
_credentials = credentials;
if (_table)
[_table reloadData];
}

- (void)_enableTable
{
[_table setUserInteractionEnabled:YES];
}

- (UITableView *)tableView
{
if (_table == nil) {
_table = [[UITableView alloc] init];
[_table setTranslatesAutoresizingMaskIntoConstraints:NO];
[_table setAutoresizingMask:UIViewAutoresizingNone];
[_table setBackgroundColor:[UIColor systemBackgroundColor]];
[_table setSeparatorStyle:UITableViewCellSeparatorStyleNone];
}
[_table sizeToFit];

return (UITableView *)_table;
}

-(void)loadView
{

UIView* view = [[UIView alloc] init];

UITableView* table = [self tableView];
[table setDelegate: self];
[table setDataSource: self];

[view addSubview: table];

CFErrorRef error = NULL;
audit_token_t auditToken = {};
memset(&auditToken, 0, sizeof(auditToken));
CFArrayRef credentialList = swca_copy_pairs(swca_copy_pairs_request_id, &auditToken, &error);
if (error) {
NSLog(@"Unable to get accounts: %@", [(__bridge NSError*)error localizedDescription]);
}

[self setCredentials:(__bridge NSArray*)credentialList];
if (credentialList) {
CFRelease(credentialList);
}

table.frame = CGRectMake(0.0, 0.0, 300.0, 45.0);
[table layoutIfNeeded]; // so autolayout can do its thing and then we can measure a cell
UITableViewCell* cell = table.visibleCells.firstObject;
CGFloat heightForOneRow = cell ? CGRectGetHeight(cell.frame) : 45.0;

NSDictionary* views = NSDictionaryOfVariableBindings(table);

if ([_credentials count] > 2)
{

CGFloat manyCredentialsHeight = CGFloatMax((heightForOneRow * 2) + 30.0, 120.0);
NSDictionary *metrics = @{@"height":@(manyCredentialsHeight)};
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[table]-|" options:0 metrics:metrics views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]];

CGFloat scale = [[UIScreen mainScreen] scale];
table.layer.borderWidth = 1.0 / scale;
table.layer.borderColor = [UIColor opaqueSeparatorColor].CGColor;

[self setPreferredContentSize:CGSizeMake(0, manyCredentialsHeight + 20.0)];

} else if ([_credentials count] == 2) {

NSDictionary *metrics = @{@"height":@(heightForOneRow * 2)};
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|" options:0 metrics:metrics views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]];

[self setPreferredContentSize:CGSizeMake(0, heightForOneRow * 2)];
[table setScrollEnabled: NO];

} else { // [_credentials count] == 1

NSDictionary *metrics = @{@"height":@(heightForOneRow)};
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|" options:0 metrics:metrics views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]|" options:0 metrics:metrics views:views]];

[self setPreferredContentSize:CGSizeMake(0, heightForOneRow)];
[table setScrollEnabled: NO];

}

[self setView:view];
}

-(void)viewWillAppear:(BOOL)animated
{

// Select the first cell by default

NSDictionary *dict = [_credentials objectAtIndex: 0];
_selectedDict = dict;
_selectedCell = [NSIndexPath indexPathForItem:0 inSection: 0];
SWCItemCell *cell = (SWCItemCell *)[_table cellForRowAtIndexPath: _selectedCell];
[cell setTicked: YES];
[cell layoutSubviews];
[_table selectRowAtIndexPath: _selectedCell animated: NO scrollPosition: UITableViewScrollPositionTop];

CFErrorRef error = NULL;
audit_token_t auditToken = {};
memset(&auditToken, 0, sizeof(auditToken));
bool result = swca_set_selection(swca_set_selection_request_id,
&auditToken, (__bridge CFDictionaryRef)dict, &error);
if (!result) {
NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]);
}

[super viewWillAppear:animated];
}

//
// UITableView delegate methods
//

- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
return [_credentials count];
}

- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = indexPath.row;

NSDictionary *dict = [_credentials objectAtIndex:row];
_selectedDict = dict;

CFErrorRef error = NULL;
audit_token_t auditToken = {};
memset(&auditToken, 0, sizeof(auditToken));
bool result = swca_set_selection(swca_set_selection_request_id,
&auditToken, (__bridge CFDictionaryRef)dict, &error);
if (!result) {
NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]);
}

_selectedCell = indexPath;
SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath];
[cell setTicked: YES];
[cell layoutSubviews];
}

- (void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath];
[cell setTicked: NO];
[cell layoutSubviews];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

NSDictionary *dict = [_credentials objectAtIndex:[indexPath row]];
SWCItemCell *cell = [[SWCItemCell alloc] initWithDictionary:dict];

// show separator on top cell if there's only or or two items
if ([_credentials count] <= 2) {
cell.showTopSeparator = YES;
} else {
cell.showSeparator = YES;

if (indexPath.row == 0)
{
cell.showTopSeparator = YES;
}

}

if (_selectedCell == indexPath)
{
[cell setTicked: YES];
} else {
[cell setTicked: NO];
}

return cell;
}

@end