Skip to content
Snippets Groups Projects
TokenViewController.m 15.1 KiB
Newer Older
Petr Hruška's avatar
Petr Hruška committed
//
//  TokenViewController.m
//  ISDS
//
//  Created by Petr Hruška on 6/30/11.
//  Copyright 2011 CZ.NIC, z.s.p.o. All rights reserved.
//

#import "TokenViewController.h"
#import "OtpStatus.h"
#import "HotpCookieTask.h"
#import "TotpCookieTask.h"
Petr Hruška's avatar
Petr Hruška committed
#import "RequestManager.h"
#import "SmsTask.h"
#import "DetailViewController.h"
#import "ISDSAppDelegate_iPad.h"
Petr Hruška's avatar
Petr Hruška committed

#define CONTINUE_BUTTON_INDEX      0
#define CANCEL_BUTTON_INDEX        1

@implementation TokenViewController

@synthesize data=_data;
@synthesize cell=_cell;
@synthesize tableView=_tableView;
@synthesize doneButton=_doneButton;
@synthesize cancelButton=_cancelButton;
Petr Hruška's avatar
Petr Hruška committed
@synthesize editing=_editing;
@synthesize delegate;
@synthesize pendingRequests=pendingRequests_;
@synthesize progressViewController=progressViewController_;
@synthesize initialTasks = _initialTasks;
@synthesize whereToPop=_whereToPop;
@synthesize prompt=_prompt;
@synthesize popAnimated=_popAnimated;
@synthesize actionSheet=_actionSheet;
Petr Hruška's avatar
Petr Hruška committed
@synthesize displayContext=_displayContext;


- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundleOrNil
{
    if ((self = [super initWithNibName:nibName bundle:nibBundleOrNil])) {
        self.popAnimated = TRUE;
    }
    
    return self;
}

- (void)incrementPendingRequests:(NSUInteger)count
{
    if (!self.pendingRequests) {
        [self.progressViewController registerObserver];
        self.toolbarItems = [self progressToolbarItems];
        [self.navigationController setToolbarHidden:NO animated:YES];
    }
    self.pendingRequests += count;
    
}

- (void)decrementPendingRequests
{
    self.pendingRequests--;
    if (!self.pendingRequests) {
        [self.navigationController setToolbarHidden:YES animated:YES];
        self.toolbarItems = [NSArray array];
        [self.progressViewController unregisterObserver];
        self.progressViewController.errorCount = 0;
    }
}
Petr Hruška's avatar
Petr Hruška committed

- (void)cancelNetworkOperations
{
    RequestManager* rm = [RequestManager requestManager];
    for (OtpStatus* status in self.data) {
        if (status.task) {
            [rm removeTask:status.task];
            [self decrementPendingRequests];
            status.task = nil;
        }
    }
}

- (void)stopTimers
{
    for (OtpStatus* status in self.data) {
        [status stopTimer];
    }
}

- (void)dismissActionSheet
{
    [self.actionSheet dismissWithClickedButtonIndex:-1 animated:NO];
Petr Hruška's avatar
Petr Hruška committed
- (void)dealloc
{
    [self cancelNetworkOperations];
    [self stopTimers];
Petr Hruška's avatar
Petr Hruška committed
    [_data release];
    [_tableView release];
    [_doneButton release];
    [_cancelButton release];
Petr Hruška's avatar
Petr Hruška committed
    [_editing release];
    [progressToolbarItems_ release];
    [progressViewController_ release];
    [_initialTasks release];
    [_actionSheet release];
Petr Hruška's avatar
Petr Hruška committed
    [super dealloc];
}

- (void)viewDidUnload
{
    self.tableView = nil;
    self.doneButton = nil;
    self.cancelButton = nil;
    [progressToolbarItems_ release];
    progressToolbarItems_ = nil;
Petr Hruška's avatar
Petr Hruška committed
    [super viewDidUnload];
}

- (TokenCell*)cellAtIndex:(NSUInteger)index
{
    return (TokenCell*)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:index]];
}

/* return [self.data count] if the focus should not be set */
- (NSUInteger)initialFocusIndex
{
    NSUInteger index = 0;
    const NSUInteger count = [self.data count];
    
    while (index < count) {
        OtpStatus* status = [self.data objectAtIndex:index];
        
        if (status.status == OtpStatusShouldSendSms) {
            return count;
        } else if (status.status == OtpStatusEnterToken) {
            return index;
        }
        index++;
    }
    return index;
}

- (void)viewDidLoad
{
    self.doneButton.title = NSLocalizedString(@"Update", nil);
    self.cancelButton.title = NSLocalizedString(@"Cancel", nil);
}

- (enum ProgressDisplayContext)currentContext
{
Petr Hruška's avatar
Petr Hruška committed
    if (self.displayContext != ProgressDisplayContextAuto) return self.displayContext;
    
    ISDSAppDelegate_Shared* appDelegate = (ISDSAppDelegate_Shared*)[[UIApplication sharedApplication] delegate];
    if ([appDelegate iPad]) {
        ISDSAppDelegate_iPad* iPadDelegate = (ISDSAppDelegate_iPad*)appDelegate;
        DetailViewController* detailViewController = iPadDelegate.detailViewController;
        if ([detailViewController popoverMode])
            return ProgressDisplayContextPopover;
        else
            return ProgressDisplayContext_iPad;
    } else
        return ProgressDisplayContext_iPhone;
}

Petr Hruška's avatar
Petr Hruška committed
- (void)viewWillAppear:(BOOL)animated
{
    self.navigationItem.title = NSLocalizedString(@"Authorization", nil);    
    self.navigationItem.prompt = self.prompt;
Petr Hruška's avatar
Petr Hruška committed
    self.navigationItem.rightBarButtonItem = self.doneButton;
    if (self.whereToPop)
        self.navigationItem.leftBarButtonItem = self.cancelButton;
    
    [self.progressViewController setDisplayContext:[self currentContext]];
    [self.navigationController setToolbarHidden:self.pendingRequests == 0];
Petr Hruška's avatar
Petr Hruška committed
    
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    /* set focus */
    NSUInteger index = [self initialFocusIndex];
    if (index < [self.data count]) {
        TokenCell* cell = [self cellAtIndex:index];
        [cell setFocus];
    }

    [super viewDidAppear:animated];
}

- (NSUInteger)indexFromTask:(OtpTask*)task
{
    const NSUInteger count = [self.data count];
    for (NSUInteger index = 0; index < count; index++) {
        OtpStatus* status = [self.data objectAtIndex:index];
        if (status.task == task) return index;
    }
    return count;
}

- (void)setupAccounts:(NSArray*)accounts
{
    self.data = [NSMutableArray arrayWithCapacity:[accounts count]];
    for (ISDSAccount* account in accounts) {
        if (account.authType != ISDSAccountAuthPass) {
            OtpStatus* status = [[OtpStatus alloc] initWithAccount:account];
            status.delegate = self;
Petr Hruška's avatar
Petr Hruška committed
            [self.data addObject:status];
            [status release];
        }
    }
}

- (void)reloadSectionForIndex:(NSUInteger)index
{
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:index] withRowAnimation:UITableViewRowAnimationFade];
}

- (void)cookieExpiredForStatus:(OtpStatus*)status
{
    NSUInteger index = [self.data indexOfObject:status];
    if (index == NSNotFound) return;
    [self reloadSectionForIndex:index];
}


Petr Hruška's avatar
Petr Hruška committed
- (void)configureCell:(TokenCell*)cell withStatus:(OtpStatus*)status
{
    cell.sendButton.titleLabel.text = NSLocalizedString(@"Send SMS", nil);
    cell.tokenText.placeholder = NSLocalizedString(@"enter token", nil);
    
Petr Hruška's avatar
Petr Hruška committed
    cell.boxName = [status.account boxName];
    
    switch (status.status) {
Petr Hruška's avatar
Petr Hruška committed
        case OtpStatusShouldSendSms:
            break;
        case OtpStatusRequestingSms:
            cell.message = NSLocalizedString(@"requesting sms", nil);
            break;
        case OtpStatusEnterToken:
            break;
        case OtpStatusRequestingCookie:
            cell.message = NSLocalizedString(@"requesting cookie", nil);
            break;
        case OtpStatusHasCookie:
            cell.message = NSLocalizedString(@"account authorized", nil);
            break;
    }
    
    cell.errorDescription = status.errorDescription;
Petr Hruška's avatar
Petr Hruška committed

    [cell setStatus:status.status];
}

- (NSArray*)progressToolbarItems
{
    if (!progressToolbarItems_) {
        UIBarButtonItem* item = [[UIBarButtonItem alloc] initWithCustomView:self.progressViewController.view];
        [self.progressViewController setDisplayContext:[self currentContext]];
        [self.progressViewController setProgressViewWidth:self.view.frame.size.width];
        progressToolbarItems_ = [[NSArray arrayWithObject:item] retain];
        [item release];
    }
    
    return progressToolbarItems_;
}

Petr Hruška's avatar
Petr Hruška committed
- (IBAction)sendSms:(id)sender
{   
    UIButton* button = (UIButton*)sender;
    NSUInteger index = button.tag;
Petr Hruška's avatar
Petr Hruška committed
    OtpStatus* status = [self.data objectAtIndex:index];
    SmsTask* task = [[SmsTask alloc] initWithOtpStatus:status];
    task.otpDelegate = self;
    status.task = task;
    [task release];
    status.status = OtpStatusRequestingSms;
    status.errorDescription = nil;
    
    RequestManager* rm = [RequestManager requestManager];
    [rm addTasks:[NSArray arrayWithObject:task] withPriority:YES];
    [self incrementPendingRequests:1];
    [self reloadSectionForIndex:index];

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    OtpStatus* status = [self.data objectAtIndex:[indexPath section]];
    return [TokenCell cellHeightForErrorDescription:status.errorDescription];
}


Petr Hruška's avatar
Petr Hruška committed
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString* reuseIdentifier = @"TokenCell";
    
    TokenCell* cell = (TokenCell*)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"TokenCell" owner:self options:nil];
        cell = self.cell;
        self.cell = nil;
    }
    
    OtpStatus* status = [self.data objectAtIndex:[indexPath section]];
    [self configureCell:cell withStatus:status];
    [cell setupTag:indexPath.section];
    
    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.data count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

- (OtpTask*)createTask:(OtpStatus*)status
{
    OtpTask* t;
    switch (status.account.authType) {
        case ISDSAccountAuthHotp:
            t = [[HotpCookieTask alloc] initWithOtpStatus:status];
            t.otpDelegate = self;
            [t autorelease];
            break;
        case ISDSAccountAuthTotp:
            t = [[TotpCookieTask alloc] initWithOtpStatus:status];
Petr Hruška's avatar
Petr Hruška committed
            t.otpDelegate = self;
            [t autorelease];
            break;
            
        default:
            t = nil;
            break;
    }
    
    return t;
}

- (void)done
{
    [self dismissActionSheet];
    if (self.whereToPop)
        [self.navigationController popToViewController:self.whereToPop animated:self.popAnimated];
    else
        [self.navigationController popViewControllerAnimated:self.popAnimated];
Petr Hruška's avatar
Petr Hruška committed
    [self.delegate tokenViewControllerDone:self];
}

- (void)downloadCookies
{
    NSUInteger taskCount = 0;
    
    for (OtpStatus* status in self.data) {
        if ([status tokenReady]) taskCount++;
    }
    
    if (!taskCount) {
        [self done];
        return;
    }
    
    NSMutableArray* tasks = [NSMutableArray arrayWithCapacity:taskCount];
    for (OtpStatus* status in self.data) 
        if ([status tokenReady]) {
            OtpTask* task = [self createTask:status];
            status.task = task;
            status.status = OtpStatusRequestingCookie;
            status.errorDescription = nil;
Petr Hruška's avatar
Petr Hruška committed
            [tasks addObject:task];
        }
    
    RequestManager* rm = [RequestManager requestManager];
    [rm addTasks:tasks withPriority:YES];
    [self incrementPendingRequests:[tasks count]];
    [self.tableView reloadData];
Petr Hruška's avatar
Petr Hruška committed
}

- (void)showActionSheet
{
    if (self.actionSheet) [self dismissActionSheet];
Petr Hruška's avatar
Petr Hruška committed
    self.actionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Some accounts are missing tokens. Do you wish to continue?", nil) delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) destructiveButtonTitle:NSLocalizedString(@"Continue", nil) otherButtonTitles: nil];
    [self.actionSheet showInView:self.view];
    [self.actionSheet release];
Petr Hruška's avatar
Petr Hruška committed
}

- (void)markMissingUnwanted
{
    for (OtpStatus* status in self.data) {
        if (![status tokenReady] && status.shouldHaveCookie) status.shouldHaveCookie = FALSE;
    }
}

- (IBAction)doneTouched
{
Petr Hruška's avatar
Petr Hruška committed
    if (self.pendingRequests) return;
    
Petr Hruška's avatar
Petr Hruška committed
    if (self.editing)
        [self.editing resignFirstResponder];
    
    NSUInteger missingCount = 0;
    for (OtpStatus* status in self.data) {
        if (status.status != OtpStatusHasCookie && ![status tokenReady] && status.shouldHaveCookie) missingCount++;
Petr Hruška's avatar
Petr Hruška committed
    }
    
    if (missingCount)
        [self showActionSheet];
    else
Petr Hruška's avatar
Petr Hruška committed
        [self downloadCookies];
}

- (IBAction)cancelTouched
Petr Hruška's avatar
Petr Hruška committed
{
    [self done];
Petr Hruška's avatar
Petr Hruška committed
}

- (BOOL)hasAllWantedCookies
{
    for (OtpStatus* status in self.data)
        if (status.status != OtpStatusHasCookie && status.shouldHaveCookie) return NO;
        
    return YES;
}

#pragma mark - OtpTaskDelegate

- (void)taskSuccessfullyDone:(ISDSTask*)task
{
    NSUInteger index = [self indexFromTask:(OtpTask*)task];
    if (index == [self.data count]) return;
Petr Hruška's avatar
Petr Hruška committed
    OtpStatus* status = [self.data objectAtIndex:index];
    switch (status.status) {
        case OtpStatusRequestingSms:
            status.status = OtpStatusEnterToken;
            [self decrementPendingRequests];
            status.task = nil;
Petr Hruška's avatar
Petr Hruška committed
            [self reloadSectionForIndex:index];
            break;
        case OtpStatusRequestingCookie:
            status.status = OtpStatusHasCookie;
            [self decrementPendingRequests];
            status.task = nil;
            if ([self hasAllWantedCookies] && !self.pendingRequests)
Petr Hruška's avatar
Petr Hruška committed
                [self done];
            else
                [self reloadSectionForIndex:index];
            break;
            
        default:
            break;
    }
}

- (void)taskFailed:(ISDSTask*)task
{
    NSUInteger index = [self indexFromTask:(OtpTask*)task];
    if (index == [self.data count]) return;
    OtpStatus* status = [self.data objectAtIndex:index];
    status.errorDescription = task.errorDescription;
    
    // decrement of pending requests can set errorCount to zero
    self.progressViewController.errorCount++;
    
    switch (status.status) {
        case OtpStatusRequestingSms:
            status.status = OtpStatusShouldSendSms;
            [self decrementPendingRequests];
            break;
        case OtpStatusRequestingCookie:
            status.status = OtpStatusEnterToken;
            status.token = @"";
            [self decrementPendingRequests];
            break;
            
        default:
            break;
    }
Petr Hruška's avatar
Petr Hruška committed
    [self reloadSectionForIndex:index];
}

#pragma mark - UITextFieldDelegate

- (void)setupTokenFromTextField:(UITextField *)textField
{
    NSUInteger index = textField.tag;
    OtpStatus* status = [self.data objectAtIndex:index];
    status.token = textField.text;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    self.editing = textField;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self setupTokenFromTextField:textField];
    self.editing = nil;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return NO;
}


- (BOOL)textFieldShouldClear:(UITextField *)textField
{
    if ([textField isFirstResponder])
        [textField resignFirstResponder];
    else
        textField.text = @"";
    
    return NO;
}

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
Petr Hruška's avatar
Petr Hruška committed
{
    switch (buttonIndex) {
        case CONTINUE_BUTTON_INDEX:
            // we will not ask next time
            [self markMissingUnwanted];
Petr Hruška's avatar
Petr Hruška committed
            [self downloadCookies];
            break;
        case CANCEL_BUTTON_INDEX:
            break;
    }
}

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    self.actionSheet = nil;
Petr Hruška's avatar
Petr Hruška committed
    if (buttonIndex != CONTINUE_BUTTON_INDEX) {
        [self.navigationController setToolbarHidden:NO];
        [self.navigationController setToolbarHidden:YES];
    }
Petr Hruška's avatar
Petr Hruška committed
@end