2017-04-05 20:56:22 +00:00
|
|
|
|
//==============================================================================
|
|
|
|
|
// This file is part of Master Password.
|
|
|
|
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
2014-03-20 11:15:37 +00:00
|
|
|
|
//
|
2017-04-05 20:56:22 +00:00
|
|
|
|
// Master Password is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
2014-03-20 11:15:37 +00:00
|
|
|
|
//
|
2017-04-05 20:56:22 +00:00
|
|
|
|
// Master Password is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License for more details.
|
2014-03-20 11:15:37 +00:00
|
|
|
|
//
|
2017-04-05 20:56:22 +00:00
|
|
|
|
// You can find a copy of the GNU General Public License in the
|
|
|
|
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
//==============================================================================
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
#import "MPSiteCell.h"
|
2014-03-20 11:15:37 +00:00
|
|
|
|
#import "MPiOSAppDelegate.h"
|
|
|
|
|
#import "MPAppDelegate_Store.h"
|
2014-09-21 14:29:18 +00:00
|
|
|
|
#import "MPAppDelegate_InApp.h"
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
@interface MPSiteCell()
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
|
|
|
|
@property(nonatomic, strong) IBOutlet UILabel *siteNameLabel;
|
|
|
|
|
@property(nonatomic, strong) IBOutlet UITextField *passwordField;
|
2014-09-08 04:15:18 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIView *loginNameContainer;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UITextField *loginNameField;
|
2017-04-15 14:57:52 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UILabel *loginNameGenerated;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UILabel *strengthLabel;
|
|
|
|
|
@property(nonatomic, strong) IBOutlet UILabel *counterLabel;
|
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *counterButton;
|
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *upgradeButton;
|
2014-09-22 02:45:21 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *answersButton;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *modeButton;
|
2014-07-22 02:57:57 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *editButton;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView;
|
2014-09-08 04:15:18 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *contentButton;
|
|
|
|
|
@property(nonatomic, strong) IBOutlet UIButton *loginNameButton;
|
2017-06-03 15:57:41 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UILabel *loginNameHint;
|
2014-08-22 01:51:47 +00:00
|
|
|
|
@property(nonatomic, strong) IBOutlet UIView *indicatorView;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
@property(nonatomic) MPSiteCellMode mode;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
@property(nonatomic, copy) NSString *transientSite;
|
2017-05-07 22:36:01 +00:00
|
|
|
|
@property(nonatomic, strong) NSManagedObjectID *siteOID;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
@implementation MPSiteCell
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
|
|
|
|
#pragma mark - Life cycle
|
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
- (void)awakeFromNib {
|
|
|
|
|
|
|
|
|
|
[super awakeFromNib];
|
|
|
|
|
|
|
|
|
|
[self addGestureRecognizer:
|
|
|
|
|
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doRevealPassword: )]];
|
|
|
|
|
[self.counterButton addGestureRecognizer:
|
|
|
|
|
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doResetCounter: )]];
|
2015-02-28 15:01:41 +00:00
|
|
|
|
[self.upgradeButton addGestureRecognizer:
|
|
|
|
|
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doDowngrade: )]];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[self setupLayer];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPSiteCell *self) {
|
2014-09-08 04:15:18 +00:00
|
|
|
|
if (from && !CGSizeEqualToSize( [from CGRectValue].size, [to CGRectValue].size ))
|
2017-05-07 22:36:01 +00:00
|
|
|
|
[self setupLayer];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
}];
|
|
|
|
|
[self.contentButton observeKeyPath:@"highlighted"
|
|
|
|
|
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
|
2014-09-16 04:34:22 +00:00
|
|
|
|
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
|
|
|
|
button.layer.shadowOpacity = button.selected? 0.7f: button.highlighted? 0.3f: 0;
|
|
|
|
|
} completion:nil];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
}];
|
|
|
|
|
[self.contentButton observeKeyPath:@"selected"
|
|
|
|
|
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
|
2014-09-16 04:34:22 +00:00
|
|
|
|
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
|
|
|
|
button.layer.shadowOpacity = button.selected? 0.7f: button.highlighted? 0.3f: 0;
|
|
|
|
|
} completion:nil];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
}];
|
|
|
|
|
[self.loginNameButton observeKeyPath:@"highlighted"
|
2014-07-22 02:57:57 +00:00
|
|
|
|
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
|
2014-09-16 04:34:22 +00:00
|
|
|
|
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
|
|
|
|
button.backgroundColor = [button.backgroundColor colorWithAlphaComponent:
|
|
|
|
|
button.selected || button.highlighted? 0.1f: 0];
|
|
|
|
|
button.layer.shadowOpacity = button.selected? 0.7f: button.highlighted? 0.3f: 0;
|
|
|
|
|
} completion:nil];
|
2014-08-22 01:51:47 +00:00
|
|
|
|
}];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[self.loginNameButton observeKeyPath:@"selected"
|
2014-07-22 02:57:57 +00:00
|
|
|
|
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
|
2014-09-16 04:34:22 +00:00
|
|
|
|
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
|
|
|
|
button.backgroundColor = [button.backgroundColor colorWithAlphaComponent:
|
|
|
|
|
button.selected || button.highlighted? 0.1f: 0];
|
|
|
|
|
button.layer.shadowOpacity = button.selected? 0.7f: button.highlighted? 0.3f: 0;
|
|
|
|
|
} completion:nil];
|
2014-08-22 01:51:47 +00:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
|
|
|
|
|
animation.byValue = @(10);
|
|
|
|
|
animation.repeatCount = HUGE_VALF;
|
|
|
|
|
animation.autoreverses = YES;
|
|
|
|
|
animation.duration = 0.3f;
|
|
|
|
|
[self.indicatorView.layer addAnimation:animation forKey:@"bounce"];
|
2014-03-20 11:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
- (void)setupLayer {
|
|
|
|
|
|
2014-09-16 04:34:22 +00:00
|
|
|
|
self.contentView.frame = self.bounds;
|
2014-09-08 04:15:18 +00:00
|
|
|
|
self.contentButton.layer.cornerRadius = 4;
|
|
|
|
|
self.contentButton.layer.shadowOffset = CGSizeZero;
|
|
|
|
|
self.contentButton.layer.shadowRadius = 5;
|
|
|
|
|
self.contentButton.layer.shadowOpacity = 0;
|
|
|
|
|
self.contentButton.layer.shadowColor = [UIColor whiteColor].CGColor;
|
|
|
|
|
self.contentButton.layer.borderWidth = 1;
|
|
|
|
|
self.contentButton.layer.borderColor = [UIColor colorWithWhite:0.15f alpha:0.6f].CGColor;
|
|
|
|
|
self.loginNameButton.layer.cornerRadius = 4;
|
|
|
|
|
self.loginNameButton.layer.shadowOffset = CGSizeZero;
|
|
|
|
|
self.loginNameButton.layer.shadowRadius = 5;
|
|
|
|
|
self.loginNameButton.layer.shadowOpacity = 0;
|
|
|
|
|
self.loginNameButton.layer.shadowColor = [UIColor whiteColor].CGColor;
|
|
|
|
|
self.contentView.layer.shadowRadius = 5;
|
|
|
|
|
self.contentView.layer.shadowOpacity = 1;
|
|
|
|
|
self.contentView.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.6f].CGColor;
|
|
|
|
|
self.contentView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.contentView.bounds cornerRadius:4].CGPath;
|
|
|
|
|
self.contentView.layer.masksToBounds = NO;
|
|
|
|
|
self.contentView.clipsToBounds = NO;
|
|
|
|
|
self.layer.masksToBounds = NO;
|
|
|
|
|
self.clipsToBounds = NO;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-22 02:57:57 +00:00
|
|
|
|
- (void)prepareForReuse {
|
2014-04-07 03:34:18 +00:00
|
|
|
|
|
2014-07-22 02:57:57 +00:00
|
|
|
|
[super prepareForReuse];
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
self.siteOID = nil;
|
2020-05-23 16:14:22 +00:00
|
|
|
|
self.queryGroups = nil;
|
2014-08-22 01:51:47 +00:00
|
|
|
|
self.transientSite = nil;
|
2014-07-26 05:26:33 +00:00
|
|
|
|
self.mode = MPPasswordCellModePassword;
|
|
|
|
|
[self updateAnimated:NO];
|
2014-03-20 11:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-16 04:34:22 +00:00
|
|
|
|
- (void)dealloc {
|
|
|
|
|
|
2014-09-24 05:07:02 +00:00
|
|
|
|
[self removeKeyPathObservers];
|
2014-09-16 04:34:22 +00:00
|
|
|
|
[self.contentButton removeKeyPathObservers];
|
|
|
|
|
[self.loginNameButton removeKeyPathObservers];
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
#pragma mark - State
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
2020-05-23 16:14:22 +00:00
|
|
|
|
- (void)setQueryGroups:(NSArray *)queryGroups {
|
2014-11-03 17:11:46 +00:00
|
|
|
|
|
2020-05-23 16:14:22 +00:00
|
|
|
|
if (self.queryGroups == queryGroups)
|
2014-11-03 17:11:46 +00:00
|
|
|
|
return;
|
2020-05-23 16:14:22 +00:00
|
|
|
|
_queryGroups = queryGroups;
|
2014-11-03 17:11:46 +00:00
|
|
|
|
|
|
|
|
|
[self updateSiteName:[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]];
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
- (void)setMode:(MPSiteCellMode)mode animated:(BOOL)animated {
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
if (self.mode == mode)
|
2014-07-21 03:54:32 +00:00
|
|
|
|
return;
|
|
|
|
|
_mode = mode;
|
2017-05-07 22:36:01 +00:00
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[self updateAnimated:animated];
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
self.siteOID = site.permanentObjectID;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[self updateAnimated:animated];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated {
|
|
|
|
|
|
|
|
|
|
self.transientSite = siteName;
|
|
|
|
|
[self updateAnimated:animated];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - UITextFieldDelegate
|
|
|
|
|
|
|
|
|
|
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
if (textField == self.passwordField)
|
|
|
|
|
[self.loginNameField becomeFirstResponder];
|
|
|
|
|
else
|
|
|
|
|
[textField resignFirstResponder];
|
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)textFieldDidBeginEditing:(UITextField *)textField {
|
|
|
|
|
|
|
|
|
|
UICollectionView *collectionView = [UICollectionView findAsSuperviewOf:self];
|
|
|
|
|
[collectionView scrollToItemAtIndexPath:[collectionView indexPathForCell:self]
|
|
|
|
|
atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
|
2014-09-16 04:34:22 +00:00
|
|
|
|
|
2014-09-15 05:00:23 +00:00
|
|
|
|
if (textField == self.loginNameField)
|
2017-06-03 15:57:41 +00:00
|
|
|
|
self.loginNameHint.hidden = [self.loginNameField.attributedText length] || self.loginNameField.enabled;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-26 05:26:33 +00:00
|
|
|
|
- (IBAction)textFieldDidChange:(UITextField *)textField {
|
|
|
|
|
|
|
|
|
|
if (textField == self.passwordField) {
|
|
|
|
|
NSString *password = self.passwordField.text;
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
|
TimeToCrack timeToCrack;
|
2014-09-21 15:47:53 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
|
2014-07-28 22:53:50 +00:00
|
|
|
|
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
|
2014-09-21 15:47:53 +00:00
|
|
|
|
if ([algorithm timeToCrack:&timeToCrack passwordOfType:[self siteInContext:context].type byAttacker:attackHardware] ||
|
2014-07-26 05:26:33 +00:00
|
|
|
|
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
|
|
|
|
PearlMainQueue( ^{
|
|
|
|
|
self.strengthLabel.text = NSStringFromTimeToCrack( timeToCrack );
|
|
|
|
|
} );
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
|
|
|
|
|
2014-07-22 02:57:57 +00:00
|
|
|
|
if (textField == self.passwordField || textField == self.loginNameField) {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
textField.enabled = NO;
|
2017-04-15 14:57:52 +00:00
|
|
|
|
NSString *text = [textField.attributedText string]?: textField.text;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
2014-09-21 15:47:53 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
if (!site)
|
2014-07-21 03:54:32 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (textField == self.passwordField) {
|
2014-09-21 15:47:53 +00:00
|
|
|
|
if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key])
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
}
|
2017-04-15 14:57:52 +00:00
|
|
|
|
else if (textField == self.loginNameField) {
|
|
|
|
|
if (![text isEqualToString:[site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key]]) {
|
2014-09-29 02:15:55 +00:00
|
|
|
|
site.loginGenerated = NO;
|
|
|
|
|
site.loginName = text;
|
|
|
|
|
|
|
|
|
|
if ([text length])
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2];
|
|
|
|
|
else
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2];
|
|
|
|
|
}
|
2014-07-21 03:54:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[context saveToStore];
|
|
|
|
|
[self updateAnimated:YES];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Actions
|
|
|
|
|
|
2014-07-22 02:57:57 +00:00
|
|
|
|
- (IBAction)doDelete:(UIButton *)sender {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
|
|
|
|
if (!site)
|
2014-07-22 02:57:57 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2020-01-14 18:59:32 +00:00
|
|
|
|
UIAlertController *controller = [UIAlertController alertControllerWithTitle:strf( @"Delete %@?", site.name ) message:nil
|
|
|
|
|
preferredStyle:UIAlertControllerStyleActionSheet];
|
2020-05-12 01:28:27 +00:00
|
|
|
|
[controller.popoverPresentationController setSourceView:sender];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Delete Site" style:UIAlertActionStyleDestructive
|
|
|
|
|
handler:^(UIAlertAction *_Nonnull action) {
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
|
MPSiteEntity *site_ = [self siteInContext:context];
|
|
|
|
|
if (site_) {
|
|
|
|
|
[context deleteObject:site_];
|
|
|
|
|
[context saveToStore];
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
}]];
|
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
|
|
|
|
[UIApp.keyWindow.rootViewController presentViewController:controller animated:YES completion:nil];
|
2014-07-22 02:57:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)doChangeType:(UIButton *)sender {
|
|
|
|
|
|
|
|
|
|
[self setMode:MPPasswordCellModePassword animated:YES];
|
|
|
|
|
|
2017-04-20 01:58:10 +00:00
|
|
|
|
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Change Password Type" message:nil
|
|
|
|
|
preferredStyle:UIAlertControllerStyleActionSheet];
|
2020-05-12 01:28:27 +00:00
|
|
|
|
[controller.popoverPresentationController setSourceView:sender];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) {
|
|
|
|
|
MPResultType type = (MPResultType)[typeNumber unsignedIntegerValue];
|
|
|
|
|
NSString *typeName = [mainSite.algorithm nameOfType:type];
|
|
|
|
|
NSString *title = type == mainSite.type? strf( @"● %@", typeName ): typeName;
|
|
|
|
|
|
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:
|
|
|
|
|
^(UIAlertAction *_Nonnull action) {
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
site = [[MPiOSAppDelegate get] changeSite:site saveInContext:context toType:type];
|
|
|
|
|
[self setSite:site animated:YES];
|
|
|
|
|
}];
|
|
|
|
|
}]];
|
|
|
|
|
}
|
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
|
|
|
|
[UIApp.keyWindow.rootViewController presentViewController:controller animated:YES completion:nil];
|
2014-03-20 11:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-22 02:57:57 +00:00
|
|
|
|
- (IBAction)doEdit:(UIButton *)sender {
|
2014-03-20 11:15:37 +00:00
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
self.loginNameField.enabled = YES;
|
|
|
|
|
self.passwordField.enabled = YES;
|
|
|
|
|
|
2017-08-13 01:57:47 +00:00
|
|
|
|
if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPResultTypeClassStateful)
|
2014-07-22 02:57:57 +00:00
|
|
|
|
[self.passwordField becomeFirstResponder];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
else
|
|
|
|
|
[self.loginNameField becomeFirstResponder];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)doMode:(UIButton *)sender {
|
|
|
|
|
|
|
|
|
|
switch (self.mode) {
|
|
|
|
|
case MPPasswordCellModePassword:
|
|
|
|
|
[self setMode:MPPasswordCellModeSettings animated:YES];
|
|
|
|
|
break;
|
|
|
|
|
case MPPasswordCellModeSettings:
|
|
|
|
|
[self setMode:MPPasswordCellModePassword animated:YES];
|
|
|
|
|
break;
|
|
|
|
|
}
|
2014-03-20 11:15:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
- (IBAction)doUpgrade:(UIButton *)sender {
|
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
2015-02-28 15:01:41 +00:00
|
|
|
|
MPSiteEntity *siteEntity = [self siteInContext:context];
|
|
|
|
|
if (![siteEntity tryMigrateExplicitly:YES]) {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[context saveToStore];
|
2015-02-28 15:01:41 +00:00
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Upgraded to V%d", siteEntity.algorithm.version )
|
|
|
|
|
dismissAfter:2];
|
|
|
|
|
[self updateAnimated:YES];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)doDowngrade:(UILongPressGestureRecognizer *)recognizer {
|
|
|
|
|
|
|
|
|
|
if (recognizer.state != UIGestureRecognizerStateBegan)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (![[MPiOSConfig get].allowDowngrade boolValue])
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
|
MPSiteEntity *siteEntity = [self siteInContext:context];
|
|
|
|
|
if (siteEntity.algorithm.version <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
siteEntity.algorithm = MPAlgorithmForVersion( siteEntity.algorithm.version - 1 );
|
|
|
|
|
[context saveToStore];
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Downgraded to V%d", siteEntity.algorithm.version )
|
|
|
|
|
dismissAfter:2];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[self updateAnimated:YES];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-01 22:32:52 +00:00
|
|
|
|
- (IBAction)doAction:(UIButton *)sender {
|
2017-05-07 22:36:01 +00:00
|
|
|
|
|
2017-05-01 22:32:52 +00:00
|
|
|
|
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) {
|
|
|
|
|
MPSiteEntity *mainSite = [self siteInContext:mainContext];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Login Page" message:nil
|
|
|
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
|
[controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
|
|
|
textField.placeholder = strf( @"Login URL for %@", mainSite.name );
|
|
|
|
|
textField.text = mainSite.url;
|
|
|
|
|
}];
|
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Save" style:UIAlertActionStyleDefault
|
|
|
|
|
handler:^(UIAlertAction *_Nonnull action) {
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
NSURL *url = [NSURL URLWithString:controller.textFields.firstObject.text];
|
|
|
|
|
site.url = [url.host? url: nil absoluteString];
|
|
|
|
|
[context saveToStore];
|
|
|
|
|
}];
|
|
|
|
|
}]];
|
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
|
|
|
|
[UIApp.keyWindow.rootViewController presentViewController:controller animated:YES completion:nil];
|
2017-05-01 22:32:52 +00:00
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-21 03:54:32 +00:00
|
|
|
|
- (IBAction)doIncrementCounter:(UIButton *)sender {
|
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
2014-09-21 15:47:53 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
|
2014-07-21 03:54:32 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
++((MPGeneratedSiteEntity *)site).counter;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[context saveToStore];
|
|
|
|
|
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Generating New Password" dismissAfter:2];
|
|
|
|
|
[self updateAnimated:YES];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)doRevealPassword:(UILongPressGestureRecognizer *)recognizer {
|
|
|
|
|
|
|
|
|
|
if (recognizer.state != UIGestureRecognizerStateBegan)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (self.passwordField.secureTextEntry) {
|
|
|
|
|
self.passwordField.secureTextEntry = NO;
|
|
|
|
|
|
|
|
|
|
PearlMainQueueAfter( 3, ^{
|
|
|
|
|
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)doResetCounter:(UILongPressGestureRecognizer *)recognizer {
|
|
|
|
|
|
|
|
|
|
if (recognizer.state != UIGestureRecognizerStateBegan)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
2014-09-21 15:47:53 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
|
2014-07-21 03:54:32 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2020-05-12 01:28:27 +00:00
|
|
|
|
((MPGeneratedSiteEntity *)site).counter = MPCounterValueInitial;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[context saveToStore];
|
|
|
|
|
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2];
|
|
|
|
|
[self updateAnimated:YES];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-24 14:54:25 +00:00
|
|
|
|
- (IBAction)doContent:(UIButton *)sender {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[UIView animateWithDuration:.2f animations:^{
|
|
|
|
|
self.contentButton.selected = YES;
|
|
|
|
|
}];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
|
|
|
|
if (self.transientSite) {
|
|
|
|
|
[[UIResponder findFirstResponder] resignFirstResponder];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Create Site" message:
|
|
|
|
|
strf( @"Remember site named:\n%@", self.transientSite )
|
|
|
|
|
preferredStyle:UIAlertControllerStyleActionSheet];
|
2020-05-12 01:28:27 +00:00
|
|
|
|
[controller.popoverPresentationController setSourceView:sender];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:
|
|
|
|
|
^(UIAlertAction *_Nonnull action) {
|
2014-08-22 01:51:47 +00:00
|
|
|
|
[[MPiOSAppDelegate get]
|
2014-09-21 15:47:53 +00:00
|
|
|
|
addSiteNamed:self.transientSite completion:^(MPSiteEntity *site, NSManagedObjectContext *context) {
|
|
|
|
|
[self copyContentOfSite:site saveInContext:context];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
|
|
|
|
|
PearlMainQueueAfter( .3f, ^{
|
|
|
|
|
[UIView animateWithDuration:.2f animations:^{
|
|
|
|
|
self.contentButton.selected = NO;
|
|
|
|
|
}];
|
2014-08-22 01:51:47 +00:00
|
|
|
|
} );
|
|
|
|
|
}];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
}]];
|
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
|
|
|
|
self.contentButton.selected = NO;
|
|
|
|
|
}]];
|
|
|
|
|
[UIApp.keyWindow.rootViewController presentViewController:controller animated:YES completion:nil];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
2014-09-21 15:47:53 +00:00
|
|
|
|
[self copyContentOfSite:[self siteInContext:context] saveInContext:context];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
|
|
|
|
|
PearlMainQueueAfter( .3f, ^{
|
|
|
|
|
[UIView animateWithDuration:.2f animations:^{
|
|
|
|
|
self.contentButton.selected = NO;
|
|
|
|
|
}];
|
|
|
|
|
} );
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (IBAction)doLoginName:(id)sender {
|
|
|
|
|
|
|
|
|
|
[UIView animateWithDuration:.2f animations:^{
|
|
|
|
|
self.loginNameButton.selected = YES;
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
2014-09-21 15:47:53 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
if (![self copyLoginOfSite:site saveInContext:context]) {
|
|
|
|
|
site.loginGenerated = YES;
|
2014-09-15 05:00:23 +00:00
|
|
|
|
[context saveToStore];
|
2014-09-16 04:34:22 +00:00
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Generated" dismissAfter:2];
|
2014-09-15 05:00:23 +00:00
|
|
|
|
[self updateAnimated:YES];
|
|
|
|
|
}
|
2014-09-08 04:15:18 +00:00
|
|
|
|
|
|
|
|
|
PearlMainQueueAfter( .3f, ^{
|
|
|
|
|
[UIView animateWithDuration:.2f animations:^{
|
|
|
|
|
self.loginNameButton.selected = NO;
|
|
|
|
|
}];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
} );
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
|
|
|
|
|
|
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
|
|
|
|
|
2014-07-26 05:26:33 +00:00
|
|
|
|
if (roundf( (float)(scrollView.contentOffset.x / self.bounds.size.width) ) == 0.0f)
|
2014-07-21 03:54:32 +00:00
|
|
|
|
[self setMode:MPPasswordCellModePassword animated:YES];
|
|
|
|
|
else
|
|
|
|
|
[self setMode:MPPasswordCellModeSettings animated:YES];
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-20 11:15:37 +00:00
|
|
|
|
#pragma mark - Private
|
|
|
|
|
|
|
|
|
|
- (void)updateAnimated:(BOOL)animated {
|
|
|
|
|
|
2014-04-07 03:34:18 +00:00
|
|
|
|
if (![NSThread isMainThread]) {
|
2017-04-29 21:50:48 +00:00
|
|
|
|
PearlMainQueueOperation( ^{
|
2014-04-07 03:34:18 +00:00
|
|
|
|
[self updateAnimated:animated];
|
2017-04-29 21:50:48 +00:00
|
|
|
|
} );
|
2014-04-07 03:34:18 +00:00
|
|
|
|
return;
|
2014-03-20 11:15:37 +00:00
|
|
|
|
}
|
2014-04-07 03:34:18 +00:00
|
|
|
|
|
2020-04-12 23:13:34 +00:00
|
|
|
|
if (animated)
|
|
|
|
|
[UIView animateWithDuration:.3f animations:^{
|
|
|
|
|
[self updateWasAnimated:animated];
|
|
|
|
|
}];
|
|
|
|
|
else
|
|
|
|
|
[self updateWasAnimated:animated];
|
|
|
|
|
}
|
2017-04-15 14:57:52 +00:00
|
|
|
|
|
2020-04-12 23:13:34 +00:00
|
|
|
|
- (void)updateWasAnimated:(BOOL)animated {
|
2014-09-29 02:15:55 +00:00
|
|
|
|
|
2020-04-12 23:13:34 +00:00
|
|
|
|
Weakify( self );
|
|
|
|
|
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2020-04-12 23:13:34 +00:00
|
|
|
|
// UI
|
|
|
|
|
//self.backgroundColor = mainSite.url? [UIColor greenColor]: [UIColor redColor];
|
|
|
|
|
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
|
|
|
|
|
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
|
|
|
|
|
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
|
|
|
|
|
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
|
|
|
|
|
self.modeButton.visible = !self.transientSite;
|
|
|
|
|
self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
|
|
|
|
|
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate;
|
|
|
|
|
self.modeButton.selected = settingsMode;
|
|
|
|
|
self.strengthLabel.gone = !settingsMode;
|
|
|
|
|
self.modeScrollView.scrollEnabled = !self.transientSite;
|
|
|
|
|
[self.modeScrollView setContentOffset:CGPointMake( self.mode * self.modeScrollView.frame.size.width, 0 ) animated:animated];
|
|
|
|
|
if (!settingsMode) {
|
|
|
|
|
[self.loginNameField resignFirstResponder];
|
|
|
|
|
[self.passwordField resignFirstResponder];
|
|
|
|
|
}
|
|
|
|
|
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
|
|
|
|
|
self.loginNameHint.text = @"Tap here to ⚙ generate username or the pencil to type one";
|
|
|
|
|
else
|
|
|
|
|
self.loginNameHint.text = @"Tap the pencil to type a username";
|
|
|
|
|
|
|
|
|
|
// Site Name
|
|
|
|
|
[self updateSiteName:mainSite];
|
|
|
|
|
|
|
|
|
|
// Site Counter
|
|
|
|
|
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
|
|
|
|
|
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
|
|
|
|
|
|
|
|
|
|
// Site Login Name
|
|
|
|
|
self.loginNameField.enabled = self.passwordField.enabled = //
|
|
|
|
|
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
|
|
|
|
|
|
|
|
|
|
// Site Password
|
|
|
|
|
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
|
|
|
|
|
self.passwordField.attributedPlaceholder = stra(
|
|
|
|
|
mainSite.type & MPResultTypeClassStateful? strl( @"No password" ):
|
|
|
|
|
mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{
|
|
|
|
|
NSForegroundColorAttributeName: [UIColor whiteColor]
|
2014-07-21 03:54:32 +00:00
|
|
|
|
} );
|
2020-04-12 23:13:34 +00:00
|
|
|
|
|
|
|
|
|
// Calculate Fields
|
|
|
|
|
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
|
MPKey *key = [MPiOSAppDelegate get].key;
|
|
|
|
|
if (!key) {
|
|
|
|
|
wrn( @"Could not load cell content: key unavailable." );
|
2017-04-29 21:50:48 +00:00
|
|
|
|
PearlMainQueueOperation( ^{
|
|
|
|
|
Strongify( self );
|
2020-04-12 23:13:34 +00:00
|
|
|
|
[self updateAnimated:animated];
|
2017-04-29 21:50:48 +00:00
|
|
|
|
} );
|
2020-04-12 23:13:34 +00:00
|
|
|
|
return;
|
2017-04-29 21:50:48 +00:00
|
|
|
|
}
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2020-04-12 23:13:34 +00:00
|
|
|
|
MPSiteEntity *site = [self siteInContext:context];
|
|
|
|
|
BOOL loginGenerated = site.loginGenerated;
|
|
|
|
|
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
|
|
|
|
|
MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
|
|
|
|
|
if (self.transientSite && transientType & MPResultTypeClassTemplate)
|
|
|
|
|
password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType
|
|
|
|
|
withCounter:1 usingKey:key];
|
|
|
|
|
else if (site)
|
|
|
|
|
password = [site resolvePasswordUsingKey:key];
|
|
|
|
|
|
|
|
|
|
TimeToCrack timeToCrack;
|
|
|
|
|
NSString *timeToCrackString = nil;
|
|
|
|
|
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
|
|
|
|
|
MPAttacker attackHardware = [[MPConfig get].siteAttacker integerValue];
|
|
|
|
|
if ([algorithm timeToCrack:&timeToCrack passwordOfType:site.type byAttacker:attackHardware] ||
|
|
|
|
|
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
|
|
|
|
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
|
|
|
|
|
|
|
|
|
|
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
|
|
|
|
|
|
|
|
|
|
PearlMainQueue( ^{
|
|
|
|
|
self.passwordField.text = password;
|
|
|
|
|
self.strengthLabel.text = timeToCrackString;
|
|
|
|
|
self.loginNameGenerated.hidden = !loginGenerated;
|
|
|
|
|
self.loginNameField.attributedText =
|
|
|
|
|
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
|
|
|
|
|
self.loginNameHint.hidden = [loginName length] || self.loginNameField.enabled;
|
|
|
|
|
|
|
|
|
|
if (![password length]) {
|
|
|
|
|
self.indicatorView.hidden = NO;
|
|
|
|
|
[self.indicatorView removeFromSuperview];
|
|
|
|
|
[self.modeScrollView addSubview:self.indicatorView];
|
|
|
|
|
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
|
|
|
|
|
metrics:nil views:@{
|
|
|
|
|
@"indicator": self.indicatorView,
|
|
|
|
|
@"target" : settingsMode? self.editButton: self.modeButton
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
else if (requiresExplicitMigration) {
|
|
|
|
|
self.indicatorView.hidden = NO;
|
|
|
|
|
[self.indicatorView removeFromSuperview];
|
|
|
|
|
[self.modeScrollView addSubview:self.indicatorView];
|
|
|
|
|
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
|
|
|
|
|
metrics:nil views:@{
|
|
|
|
|
@"indicator": self.indicatorView,
|
|
|
|
|
@"target" : settingsMode? self.upgradeButton: self.modeButton
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
self.indicatorView.hidden = YES;
|
|
|
|
|
} );
|
|
|
|
|
}]) {
|
|
|
|
|
wrn( @"Could not load cell content: store unavailable." );
|
|
|
|
|
PearlMainQueueOperation( ^{
|
|
|
|
|
Strongify( self );
|
|
|
|
|
[self updateAnimated:animated];
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (animated)
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[self.contentView layoutIfNeeded];
|
2014-04-07 03:34:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-03 17:11:46 +00:00
|
|
|
|
- (void)updateSiteName:(MPSiteEntity *)site {
|
|
|
|
|
|
|
|
|
|
NSString *siteName = self.transientSite?: site.name;
|
|
|
|
|
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName?: @""];
|
|
|
|
|
if ([attributedSiteName length])
|
2020-05-23 16:14:22 +00:00
|
|
|
|
for (NSUInteger f = 0, s = 0; f < [self.queryGroups count]; ++f, ++s) {
|
|
|
|
|
s = [siteName rangeOfString:self.queryGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
|
|
|
|
|
range:NSMakeRange( s, [siteName length] - s )].location;
|
2014-11-03 17:11:46 +00:00
|
|
|
|
if (s == NSNotFound)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[UIColor redColor]
|
2020-05-23 16:14:22 +00:00
|
|
|
|
range:NSMakeRange( s, [self.queryGroups[f] length] )];
|
2014-11-03 17:11:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-15 14:57:52 +00:00
|
|
|
|
if (self.transientSite)
|
|
|
|
|
[attributedSiteName appendAttributedString:stra( @" – Tap to create", @{} )];
|
|
|
|
|
|
2014-11-03 17:11:46 +00:00
|
|
|
|
self.siteNameLabel.attributedText = attributedSiteName;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
- (BOOL)copyContentOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
inf( @"Copying password for: %@", site.name );
|
|
|
|
|
NSString *password = [site resolvePasswordUsingKey:[MPAppDelegate_Shared get].key];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
if (![password length])
|
2014-09-15 05:00:23 +00:00
|
|
|
|
return NO;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
PearlMainQueue( ^{
|
2017-04-29 21:50:48 +00:00
|
|
|
|
[self.window endEditing:YES];
|
|
|
|
|
|
2017-04-27 02:01:27 +00:00
|
|
|
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
2020-01-14 18:59:32 +00:00
|
|
|
|
if (@available( iOS 10.0, * )) {
|
2017-04-27 02:01:27 +00:00
|
|
|
|
[pasteboard setItems:@[ @{ UIPasteboardTypeAutomatic: password } ]
|
|
|
|
|
options:@{
|
|
|
|
|
UIPasteboardOptionLocalOnly : @NO,
|
|
|
|
|
UIPasteboardOptionExpirationDate: [NSDate dateWithTimeIntervalSinceNow:3 * 60]
|
|
|
|
|
}];
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied (3 min)" ) dismissAfter:2];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
pasteboard.string = password;
|
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Password Copied" ) dismissAfter:2];
|
|
|
|
|
}
|
2014-09-08 04:15:18 +00:00
|
|
|
|
} );
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
[site use];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[context saveToStore];
|
2014-09-15 05:00:23 +00:00
|
|
|
|
return YES;
|
2014-09-08 04:15:18 +00:00
|
|
|
|
}
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
- (BOOL)copyLoginOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
inf( @"Copying login for: %@", site.name );
|
|
|
|
|
NSString *loginName = [site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
if (![loginName length])
|
2014-09-15 05:00:23 +00:00
|
|
|
|
return NO;
|
2014-09-08 04:15:18 +00:00
|
|
|
|
|
|
|
|
|
PearlMainQueue( ^{
|
2017-04-29 21:50:48 +00:00
|
|
|
|
[self.window endEditing:YES];
|
|
|
|
|
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[UIPasteboard generalPasteboard].string = loginName;
|
2017-04-29 21:50:48 +00:00
|
|
|
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Login Name Copied" ) dismissAfter:2];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
} );
|
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
[site use];
|
2014-09-08 04:15:18 +00:00
|
|
|
|
[context saveToStore];
|
2014-09-15 05:00:23 +00:00
|
|
|
|
return YES;
|
2014-07-21 03:54:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
|
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
|
2014-07-21 03:54:32 +00:00
|
|
|
|
|
2017-05-07 22:36:01 +00:00
|
|
|
|
return [MPSiteEntity existingObjectWithID:self.siteOID inContext:context];
|
2014-07-21 03:54:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-20 11:15:37 +00:00
|
|
|
|
@end
|