/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
*/
#import "FRABlockActionSheet.h"
#import "FRABlockAlertView.h"
#import "FRAIdentityModel.h"
#import "FRAHotpOathMechanism.h"
#import "FRAOathCode.h"
#import "FRAOathMechanismTableViewCell.h"
#import "FRAOathMechanismTableViewCellController.h"
#import "FRATotpOathMechanism.h"
/*!
* Private interface.
*/
@interface FRAOathMechanismTableViewCellController ()
/*!
* Timer for updating TOTP progress indicator and generating next code in sequence.
*/
@property (strong, nonatomic) NSTimer *progressAnimationTimer;
@end
@implementation FRAOathMechanismTableViewCellController
+ (instancetype)controllerWithView:(FRAOathMechanismTableViewCell*)view mechanism:(FRAMechanism *)mechanism {
return [[FRAOathMechanismTableViewCellController alloc] initWithView:view mechanism:mechanism];
}
- (instancetype)initWithView:(FRAOathMechanismTableViewCell*)view mechanism:(FRAMechanism *)mechanism {
if (self = [super init]) {
_tableViewCell = view;
_mechanism = mechanism;
}
return self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([self.mechanism isKindOfClass:[FRATotpOathMechanism class]]) {
[self startProgressAnimationTimer];
} else if ([self.mechanism isKindOfClass:[FRAHotpOathMechanism class]]) {
[self stopProgressAnimationTimer];
}
[self showHideElements];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self stopProgressAnimationTimer];
}
- (void)showHideElements {
if (self.isEditing) {
[UIView animateWithDuration:0.5f animations:^{
self.tableViewCell.totpCodeProgress.alpha = 0.0f;
self.tableViewCell.hotpRefreshButton.alpha = 0.0f;
}];
} else {
if ([self.mechanism isKindOfClass:[FRATotpOathMechanism class]]) {
[UIView animateWithDuration:0.5f animations:^{
self.tableViewCell.totpCodeProgress.alpha = 1.0f;
self.tableViewCell.hotpRefreshButton.alpha = 0.0f;
}];
} else if ([self.mechanism isKindOfClass:[FRAHotpOathMechanism class]]) {
[UIView animateWithDuration:0.5f animations:^{
self.tableViewCell.totpCodeProgress.alpha = 0.0f;
self.tableViewCell.hotpRefreshButton.alpha = 1.0f;
} completion:^(BOOL finished) {
self.tableViewCell.totpCodeProgress.progress = 0.0f;
}];
}
}
}
- (void)timerCallback:(NSTimer*)timer {
if ([self.mechanism isKindOfClass:[FRATotpOathMechanism class]]) {
FRATotpOathMechanism *mechanism = (FRATotpOathMechanism *)self.mechanism;
if ((!mechanism.code) || [mechanism hasExpired]) {
NSError *error;
if (![mechanism generateNextCode:&error]) {
[self showAlert];
}
}
} else if ([self.mechanism isKindOfClass:[FRAHotpOathMechanism class]]) {
FRAHotpOathMechanism *mechanism = (FRAHotpOathMechanism *)self.mechanism;
if ((!mechanism.code)) {
NSError* error;
if (![mechanism generateNextCode:&error]) {
[self showAlert];
}
}
}
[self reloadData];
}
- (void)setEditing:(BOOL)editing {
[super setEditing:editing];
[self showHideElements];
[self reloadData];
}
- (void)didTouchUpInside {
if (!self.isEditing) {
if (![self mechanismCode]) {
// if no code has been generated, allow the first to be created by touching anywhere within the cell;
// once the first code has been generated, the refresh button must be used
[self generateNextCode];
} else {
// otherwise, if a code has already been displayed, then offer to copy it to the clipboard
FRABlockActionSheet *actionSheet = [[FRABlockActionSheet alloc]
initWithTitle:nil
delegate:nil
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Copy", nil];
actionSheet.callback = ^(NSInteger offset) {
if (offset == 1) {
NSString* codeValue = [self mechanismCode];
if (codeValue) {
[[UIPasteboard generalPasteboard] setString:codeValue];
}
}
};
[actionSheet showFromRect:self.view.frame inView:self.view animated:YES];
}
}
}
- (NSString *)mechanismCode {
return (FRAHotpOathMechanism *)self.mechanism ? ((FRAHotpOathMechanism *)self.mechanism).code : ((FRATotpOathMechanism *)self.mechanism).code;
}
- (NSInteger)mechanismCodeLength {
return (FRAHotpOathMechanism *)self.mechanism ? ((FRAHotpOathMechanism *)self.mechanism).codeLength : ((FRATotpOathMechanism *)self.mechanism).codeLength;
}
- (void)generateNextCode {
if (!self.isEditing) {
NSError* error;
if ([[self mechanism] isKindOfClass:[FRAHotpOathMechanism class]]) {
if (![(FRAHotpOathMechanism *)self.mechanism generateNextCode:&error]) {
[self showAlert];
}
} else if ([[self mechanism] isKindOfClass:[FRATotpOathMechanism class]]) {
if (![(FRATotpOathMechanism *)self.mechanism generateNextCode:&error]) {
[self showAlert];
}
}
}
}
- (void)reloadData {
UIColor *seaGreen = [UIColor colorWithRed:48.0/255.0 green:160.0/255.0 blue:157.0/255.0 alpha:1.0];
UIColor *dashboardRed = [UIColor colorWithRed:169.0/255.0 green:68.0/255.0 blue:66.0/255.0 alpha:1.0];
UIColor *color = seaGreen;
// Set font color for code and (if totp-based) the progress indicator
if ([self.mechanism isKindOfClass:[FRATotpOathMechanism class]] && !self.isEditing) {
FRATotpOathMechanism *mechanism = (FRATotpOathMechanism *)[self mechanism];
float progress = mechanism.progress;
if (progress > 0.9f) {
color = dashboardRed;
}
self.tableViewCell.totpCodeProgress.progress = progress;
self.tableViewCell.totpCodeProgress.progressColor = color;
self.tableViewCell.code.textColor = color;
} else {
self.tableViewCell.code.textColor = color;
}
// Set the code text
NSString* codeValue = [@"" stringByPaddingToLength:[self mechanismCodeLength] withString:@"●" startingAtIndex:0];
NSString *mechanismCode = [self mechanismCode];
if (mechanismCode && !self.isEditing) {
codeValue = mechanismCode;
}
NSUInteger midPoint = codeValue.length / 2;
NSString *firstHalf = [codeValue substringToIndex:midPoint];
NSString *secondHalf = [codeValue substringFromIndex:midPoint];
self.tableViewCell.code.text = [NSString stringWithFormat:@"%@ %@", firstHalf, secondHalf];
}
- (void)startProgressAnimationTimer {
if (!self.progressAnimationTimer) {
self.progressAnimationTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerCallback:) userInfo:nil repeats:YES];
}
}
- (void)stopProgressAnimationTimer {
[self.progressAnimationTimer invalidate];
self.progressAnimationTimer = nil;
}
- (void)showAlert {
FRABlockAlertView *alertView = [[FRABlockAlertView alloc] initWithTitle:NSLocalizedString(@"mechanism_generate_code_error_title", nil)
message:nil
delegate:nil
cancelButtonTitle:NSLocalizedString(@"ok", nil)
otherButtonTitle:nil
handler:nil];
[alertView show];
}
@end