From 318aca4d8fb24696bcc02e4061f60a5e00a02af9 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 15 Mar 2014 20:38:14 -0400 Subject: [PATCH] WIP - new UI for Master Password. --- .idea/inspectionProfiles/Project_Default.xml | 1 + External/Pearl | 2 +- .../project.pbxproj | 24 + MasterPassword/ObjC/iOS/MPAvatarCell.h | 40 + MasterPassword/ObjC/iOS/MPAvatarCell.m | 140 +++ .../ObjC/iOS/MPCombinedViewController.h | 49 + .../ObjC/iOS/MPCombinedViewController.m | 661 +++++++++++++ .../ObjC/iOS/MPPreferencesViewController.m | 4 +- .../ObjC/iOS/MPUnlockViewController.m | 10 +- MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 8 +- .../ObjC/iOS/MainStoryboard_iPhone.storyboard | 125 ++- .../ObjC/iOS/MasterPassword-Info.plist | 2 +- .../project.pbxproj | 60 +- MasterPassword/ObjC/iOS/Storyboard.storyboard | 923 ++++++++++++++++++ .../Resources/Media/Avatars/avatar-add.png | Bin 0 -> 7770 bytes .../Resources/Media/Avatars/avatar-add@2x.png | Bin 0 -> 16583 bytes 16 files changed, 2021 insertions(+), 28 deletions(-) create mode 100644 MasterPassword/ObjC/iOS/MPAvatarCell.h create mode 100644 MasterPassword/ObjC/iOS/MPAvatarCell.m create mode 100644 MasterPassword/ObjC/iOS/MPCombinedViewController.h create mode 100644 MasterPassword/ObjC/iOS/MPCombinedViewController.m create mode 100644 MasterPassword/ObjC/iOS/Storyboard.storyboard create mode 100644 MasterPassword/Resources/Media/Avatars/avatar-add.png create mode 100644 MasterPassword/Resources/Media/Avatars/avatar-add@2x.png diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 87f2daf9..a84efcb2 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -9,6 +9,7 @@ + diff --git a/External/Pearl b/External/Pearl index 9e41cebe..575b409c 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit 9e41cebeba085fce2bbae2592d52bbfd740f9ffb +Subproject commit 575b409cca36eabfaacf0a963ed259454cb8ec66 diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj index 71e50721..c092d5e1 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj @@ -21,6 +21,12 @@ DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; }; DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; DA1E4D50176E0E280065E0EF /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1E4D4F176E0E280065E0EF /* Media.xcassets */; }; + DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; }; + DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */; }; + DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */; }; + DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */; }; + DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */; }; + DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */; }; DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; }; DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; }; DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; }; @@ -308,6 +314,12 @@ DA16B340170661DB000A0EAB /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; DA16B343170661EE000A0EAB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; DA1E4D4F176E0E280065E0EF /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = MasterPassword/Media.xcassets; sourceTree = ""; }; + DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = ""; }; + DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = ""; }; + DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = ""; }; + DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Pearl.h"; sourceTree = ""; }; + DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+PearlBlock.m"; sourceTree = ""; }; + DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+PearlBlock.h"; sourceTree = ""; }; DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = ""; }; DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = ""; }; DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = ""; }; @@ -1038,6 +1050,12 @@ DAFE45D715039823003ABA7C /* Pearl */ = { isa = PBXGroup; children = ( + DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */, + DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */, + DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */, + DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */, + DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */, + DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */, DA3509FC15F101A500C14A8E /* PearlQueue.h */, DA3509FD15F101A500C14A8E /* PearlQueue.m */, 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */, @@ -1145,6 +1163,7 @@ DAEB93FD18AB0FFD000490CC /* mdc2.h in Headers */, DAEB942718AB0FFD000490CC /* sha256.h in Headers */, DAEB940818AB0FFD000490CC /* pkcs7.h in Headers */, + DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */, DAEB93DF18AB0FFD000490CC /* bn.h in Headers */, DAEB940718AB0FFD000490CC /* pkcs12.h in Headers */, DAEB941A18AB0FFD000490CC /* txt_db.h in Headers */, @@ -1172,6 +1191,7 @@ DAFE4A2415039824003ABA7C /* PearlInfoPlist.h in Headers */, DAEB940618AB0FFD000490CC /* pem2.h in Headers */, DAFE4A2615039824003ABA7C /* PearlLogger.h in Headers */, + DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */, DAEB93FC18AB0FFD000490CC /* md5.h in Headers */, DAFE4A2815039824003ABA7C /* PearlMathUtils.h in Headers */, DAFE4A2A15039824003ABA7C /* PearlObjectUtils.h in Headers */, @@ -1242,6 +1262,7 @@ DAEB93DB18AB0FFD000490CC /* asn1_mac.h in Headers */, DAEB940518AB0FFD000490CC /* pem.h in Headers */, DAEB942818AB0FFD000490CC /* sysendian.h in Headers */, + DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */, DAEB93FF18AB0FFD000490CC /* obj_mac.h in Headers */, DAEB93E718AB0FFD000490CC /* crypto.h in Headers */, DAEB941318AB0FFD000490CC /* ssl2.h in Headers */, @@ -1607,6 +1628,7 @@ DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */, DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */, DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */, + DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */, DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */, DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */, DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */, @@ -1622,8 +1644,10 @@ DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */, DAFE4A3B15039824003ABA7C /* PearlSCrypt.m in Sources */, DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */, + DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */, DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */, DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */, + DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */, DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */, DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */, DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */, diff --git a/MasterPassword/ObjC/iOS/MPAvatarCell.h b/MasterPassword/ObjC/iOS/MPAvatarCell.h new file mode 100644 index 00000000..a3ff62cb --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPAvatarCell.h @@ -0,0 +1,40 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAvatarCell.h +// MPAvatarCell +// +// Created by lhunath on 2014-03-11. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import +#import "MPEntities.h" +@class MPAvatarCell; + +/* Avatar with a "+" symbol. */ +extern const long MPAvatarAdd; + +typedef NS_ENUM(NSUInteger, MPAvatarMode) { + MPAvatarModeLowered, + MPAvatarModeRaisedButInactive, + MPAvatarModeRaisedAndActive +}; + +@interface MPAvatarCell : UICollectionViewCell +@property (copy, nonatomic) NSString *name; +@property (assign, nonatomic) long avatar; +@property (assign, nonatomic) MPAvatarMode mode; +@property (assign, nonatomic) float visibility; + ++ (NSString *)reuseIdentifier; + +@end diff --git a/MasterPassword/ObjC/iOS/MPAvatarCell.m b/MasterPassword/ObjC/iOS/MPAvatarCell.m new file mode 100644 index 00000000..523862b6 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPAvatarCell.m @@ -0,0 +1,140 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAvatarCell.h +// MPAvatarCell +// +// Created by lhunath on 2014-03-11. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPAvatarCell.h" + +const long MPAvatarAdd = 10000; + +@interface MPAvatarCell() + +@property(strong, nonatomic) IBOutlet UIImageView *avatarImageView; +@property(strong, nonatomic) IBOutlet UILabel *nameLabel; +@property(strong, nonatomic) IBOutlet UIView *nameContainer; +@property(strong, nonatomic) IBOutlet NSLayoutConstraint *nameCenterConstraint; + +@end + +@implementation MPAvatarCell { +} + ++ (NSString *)reuseIdentifier { + + return @"MPAvatarCell"; +} + +- (void)awakeFromNib { + + [super awakeFromNib]; + + self.nameContainer.layer.cornerRadius = 5; + + self.avatarImageView.hidden = NO; + self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2; + self.avatarImageView.layer.shadowColor = [UIColor blackColor].CGColor; + self.avatarImageView.layer.shadowOpacity = 1; + self.avatarImageView.layer.shadowRadius = 15; + self.avatarImageView.layer.masksToBounds = NO; + self.avatarImageView.backgroundColor = [UIColor clearColor]; + + [self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { + [_self onSelectedOrHighlighted]; + }]; + [self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) { + [_self onSelectedOrHighlighted]; + }]; + + self.visibility = 0; + self.mode = MPAvatarModeLowered; +} + +- (void)onSelectedOrHighlighted { + + self.avatarImageView.backgroundColor = self.selected || self.highlighted? self.avatarImageView.tintColor: [UIColor clearColor]; +} + +- (void)setAvatar:(long)avatar { + + _avatar = avatar; + + if (avatar == MPAvatarAdd) + self.avatarImageView.image = [UIImage imageNamed:@"avatar-add"]; + else + self.avatarImageView.image = [UIImage imageNamed:strf( @"avatar-%ld", avatar )]; +} + +- (NSString *)name { + + return self.nameLabel.text; +} + +- (void)setName:(NSString *)name { + + self.nameLabel.text = name; +} + +- (void)setVisibility:(float)visibility { + + _visibility = visibility; + + self.nameContainer.alpha = visibility; +} + +- (void)setHighlighted:(BOOL)highlighted { + + super.highlighted = highlighted; + + [UIView animateWithDuration:0.1f animations:^{ + self.avatarImageView.transform = highlighted? CGAffineTransformMakeScale( 1.1f, 1.1f ): CGAffineTransformIdentity; + }]; +} + +- (void)setMode:(MPAvatarMode)mode { + + _mode = mode; + + [UIView animateWithDuration:0.2f animations:^{ + self.avatarImageView.transform = CGAffineTransformIdentity; + }]; + [UIView animateWithDuration:0.3f animations:^{ + switch (mode) { + + case MPAvatarModeLowered: { + self.nameCenterConstraint.priority = UILayoutPriorityDefaultLow; + self.nameContainer.backgroundColor = [UIColor clearColor]; + self.avatarImageView.alpha = 1; + break; + } + case MPAvatarModeRaisedButInactive: { + self.nameCenterConstraint.priority = UILayoutPriorityDefaultLow; + self.nameContainer.backgroundColor = [UIColor clearColor]; + self.avatarImageView.alpha = 0.3f; + break; + } + case MPAvatarModeRaisedAndActive: { + self.nameCenterConstraint.priority = UILayoutPriorityDefaultHigh; + self.nameContainer.backgroundColor = [UIColor blackColor]; + self.avatarImageView.alpha = 1; + break; + } + } + + [self.nameCenterConstraint apply]; + }]; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPCombinedViewController.h b/MasterPassword/ObjC/iOS/MPCombinedViewController.h new file mode 100644 index 00000000..00363762 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPCombinedViewController.h @@ -0,0 +1,49 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPCombinedViewController.h +// MPCombinedViewController +// +// Created by lhunath on 2014-03-08. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "LLGitTip.h" + +typedef NS_ENUM(NSUInteger, MPCombinedMode) { + MPCombinedModeUserSelection, + MPCombinedModePasswordSelection, +}; + +@interface MPCombinedViewController : UIViewController + +@property(assign, nonatomic) MPCombinedMode mode; + +#pragma mark - UserSelection + +@property(strong, nonatomic) IBOutlet UIView *userSelectionContainer; +@property(weak, nonatomic) IBOutlet UILabel *hintLabel; +@property(weak, nonatomic) IBOutlet UIView *gitTipTip; +@property(weak, nonatomic) IBOutlet LLGitTip *gitTipButton; +@property(weak, nonatomic) IBOutlet UITextField *entryField; +@property(weak, nonatomic) IBOutlet UILabel *entryLabel; +@property(weak, nonatomic) IBOutlet UIView *entryContainer; +@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView; +@property (strong, nonatomic) IBOutlet NSLayoutConstraint *avatarCollectionCenterConstraint; + +#pragma mark - PasswordSelection + +@property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer; +@property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView; + +- (IBAction)doSignOut:(UIBarButtonItem *)sender; + +@end diff --git a/MasterPassword/ObjC/iOS/MPCombinedViewController.m b/MasterPassword/ObjC/iOS/MPCombinedViewController.m new file mode 100644 index 00000000..3b77a746 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPCombinedViewController.m @@ -0,0 +1,661 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPCombinedViewController.h +// MPCombinedViewController +// +// Created by lhunath on 2014-03-08. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPCombinedViewController.h" +#import "MPEntities.h" +#import "MPAvatarCell.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" +#import "MPAppDelegate_Key.h" + +typedef NS_ENUM(NSUInteger, MPActiveUserState) { + MPActiveUserStateNone, + MPActiveUserStateLogin, + MPActiveUserStateUserName, + MPActiveUserStateMasterPasswordChoice, + MPActiveUserStateMasterPasswordConfirmation, +}; + +@interface MPCombinedViewController() + +@property(nonatomic) MPActiveUserState activeUserState; +@property(nonatomic, strong) NSArray *userIDs; +@end + +@implementation MPCombinedViewController { + __weak id _storeObserver; + __weak id _mocObserver; + NSArray *_notificationObservers; + NSString *_masterPasswordChoice; +} + +- (void)viewDidLoad { + + [super viewDidLoad]; + + self.avatarCollectionView.allowsMultipleSelection = YES; + + [self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock: + ^(id from, id to, NSKeyValueChange cause, MPCombinedViewController *_self) { + [_self updateAvatars]; + }]; + + self.mode = MPCombinedModeUserSelection; +} + +- (void)viewDidAppear:(BOOL)animated { + + [super viewDidAppear:animated]; + + [self registerObservers]; + [self updateMode]; +} + +- (void)viewWillDisappear:(BOOL)animated { + + [super viewWillDisappear:animated]; + + [self removeObservers]; + [self needStoreObserved:NO]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidEndEditing:(UITextField *)textField { +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + + if (textField == self.entryField) { + switch (self.activeUserState) { + case MPActiveUserStateNone: { + [textField resignFirstResponder]; + break; + } + case MPActiveUserStateLogin: { + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + BOOL signedIn = NO, isNew = NO; + MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew]; + if (!isNew && user) + signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context + usingMasterPassword:self.entryField.text]; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + if (!signedIn) { + // Sign in failed. + // TODO: warn user + return; + } + }]; + }]; + break; + } + case MPActiveUserStateUserName: { + NSString *userName = self.entryField.text; + if (![userName length]) { + // No name entered. + // TODO: warn user + return NO; + } + + [self selectedAvatar].name = userName; + self.activeUserState = MPActiveUserStateMasterPasswordChoice; + break; + } + case MPActiveUserStateMasterPasswordChoice: { + NSString *masterPassword = self.entryField.text; + if (![masterPassword length]) { + // No password entered. + // TODO: warn user + return NO; + } + + self.activeUserState = MPActiveUserStateMasterPasswordConfirmation; + break; + } + case MPActiveUserStateMasterPasswordConfirmation: { + NSString *masterPassword = self.entryField.text; + if (![masterPassword length]) { + // No password entered. + // TODO: warn user + return NO; + } + + if (![masterPassword isEqualToString:_masterPasswordChoice]) { + // Master password confirmation failed. + // TODO: warn user + self.activeUserState = MPActiveUserStateMasterPasswordChoice; + return NO; + } + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + BOOL isNew = NO; + MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew]; + if (isNew) { + user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] ) + inManagedObjectContext:context]; + MPAvatarCell *avatarCell = [self selectedAvatar]; + user.avatar = avatarCell.avatar; + user.name = avatarCell.name; + } + + BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context usingMasterPassword:masterPassword]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + if (!signedIn) { + // Sign in failed, shouldn't happen for a new user. + // TODO: warn user + self.activeUserState = MPActiveUserStateNone; + return; + } + }]; + }]; + + break; + } + } + } + + return NO; +} + +// This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification. +- (void)textFieldDidChange:(UITextField *)textField { + + if (textField == self.entryField) { + switch (self.activeUserState) { + case MPActiveUserStateNone: + break; + case MPActiveUserStateLogin: + break; + case MPActiveUserStateUserName: { + NSString *userName = self.entryField.text; + [self selectedAvatar].name = [userName length]? userName: strl( @"New User" ); + break; + } + case MPActiveUserStateMasterPasswordChoice: + break; + case MPActiveUserStateMasterPasswordConfirmation: + break; + } + } +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + + if (collectionView == self.avatarCollectionView) + return [self.userIDs count] + 1; + + else if (collectionView == self.passwordCollectionView) + return 0; + + Throw(@"unexpected collection view: %@", collectionView); +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + + if (collectionView == self.avatarCollectionView) { + MPAvatarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPAvatarCell reuseIdentifier] forIndexPath:indexPath]; + [cell addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPress:)]]; + [self updateAvatar:cell atIndexPath:indexPath]; + + BOOL isNew = NO; + MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady] + isNew:&isNew]; + if (isNew) { + // New User + cell.avatar = MPAvatarAdd; + cell.name = strl( @"New User" ); + } + else { + // Existing User + cell.avatar = user.avatar; + cell.name = user.name; + } + + NSArray *selectedIndexPaths = [self.avatarCollectionView indexPathsForSelectedItems]; + if (![selectedIndexPaths count]) + cell.mode = MPAvatarModeLowered; + else if ([selectedIndexPaths containsObject:indexPath]) + cell.mode = MPAvatarModeRaisedAndActive; + else + cell.mode = MPAvatarModeRaisedButInactive; + + return cell; + } + + else if (collectionView == self.passwordCollectionView) + return nil; + + Throw(@"unexpected collection view: %@", collectionView); +} + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + + if (collectionView == self.avatarCollectionView) { + [self.avatarCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally + animated:YES]; + + [UIView animateWithDuration:0.3f animations:^{ + for (NSUInteger otherItem = 0; otherItem < [collectionView numberOfItemsInSection:indexPath.section]; ++otherItem) + if (otherItem != indexPath.item) { + NSIndexPath *otherIndexPath = [NSIndexPath indexPathForItem:otherItem inSection:indexPath.section]; + [collectionView deselectItemAtIndexPath:otherIndexPath animated:YES]; + + MPAvatarCell *otherCell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:otherIndexPath]; + otherCell.mode = MPAvatarModeRaisedButInactive; + } + + MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath]; + cell.mode = MPAvatarModeRaisedAndActive; + }]; + + BOOL isNew = NO; + MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady] + isNew:&isNew]; + if (isNew) + self.activeUserState = MPActiveUserStateUserName; + else if (!user.keyID) + self.activeUserState = MPActiveUserStateMasterPasswordChoice; + else + self.activeUserState = MPActiveUserStateLogin; + } +} + +- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { + + if (collectionView == self.avatarCollectionView) { + self.activeUserState = MPActiveUserStateNone; + } +} + +#pragma mark - UILongPressGestureRecognizer + +- (void)didLongPress:(UILongPressGestureRecognizer *)recognizer { + + if ([recognizer.view isKindOfClass:[MPAvatarCell class]]) { + if (recognizer.state != UIGestureRecognizerStateBegan) + // Don't show the action menu unless the state is Began. + return; + + MPAvatarCell *avatarCell = (MPAvatarCell *)recognizer.view; + NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; + + BOOL isNew = NO; + MPUserEntity *user = [self userForAvatar:avatarCell inContext:mainContext isNew:&isNew]; + NSManagedObjectID *userID = user.objectID; + if (isNew || !user) + return; + + [PearlSheet showSheetWithTitle:user.name + viewStyle:UIActionSheetStyleBlackTranslucent + initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { + if (buttonIndex == [sheet cancelButtonIndex]) + return; + + if (buttonIndex == [sheet destructiveButtonIndex]) { + // Delete User + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + NSManagedObject *user_ = [context existingObjectWithID:userID error:NULL]; + if (user_) { + [context deleteObject:user_]; + [context saveToStore]; + } + }]; + return; + } + + if (buttonIndex == [sheet firstOtherButtonIndex]) + // Reset Password + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *user_ = (MPUserEntity *)[context existingObjectWithID:userID error:NULL]; + if (user_) + [[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{ + dbg(@"changing mp for user: %@, keyID: %@", user_.name, user_.keyID); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; + [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO + scrollPosition:UICollectionViewScrollPositionNone]; + [self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath]; + }]; + }]; + }]; + } cancelTitle:[PearlStrings get].commonButtonCancel + destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; + } +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { + + if (scrollView == self.avatarCollectionView) { + CGPoint offsetToCenter = CGPointMake( + self.avatarCollectionView.bounds.size.width / 2, + self.avatarCollectionView.bounds.size.height / 2 ); + NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForItemAtPoint: + CGPointPlusCGPoint( *targetContentOffset, offsetToCenter )]; + CGPoint targetCenter = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:avatarIndexPath].center; + *targetContentOffset = CGPointMinusCGPoint( targetCenter, offsetToCenter ); + NSAssert([self.avatarCollectionView indexPathForItemAtPoint:targetCenter].item == avatarIndexPath.item, @"should be same item"); + } +} + +- (MPAvatarCell *)selectedAvatar { + + NSArray *selectedIndexPaths = self.avatarCollectionView.indexPathsForSelectedItems; + if (![selectedIndexPaths count]) { + // No selected user. + return nil; + } + + return (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:selectedIndexPaths.firstObject]; +} + +- (MPUserEntity *)selectedUserInContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew { + + MPAvatarCell *selectedAvatar = [self selectedAvatar]; + if (!selectedAvatar) { + // No selected user. + *isNew = NO; + return nil; + } + + return [self userForAvatar:selectedAvatar inContext:context isNew:isNew]; +} + +- (MPUserEntity *)userForAvatar:(MPAvatarCell *)cell inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew { + + return [self userForIndexPath:[self.avatarCollectionView indexPathForCell:cell] inContext:context isNew:isNew]; +} + +- (MPUserEntity *)userForIndexPath:(NSIndexPath *)indexPath inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew { + + if ((*isNew = indexPath.item >= [self.userIDs count])) + return nil; + + NSError *error = nil; + MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:self.userIDs[indexPath.item] error:&error]; + if (error) + wrn(@"Failed to load user into context: %@", error); + + return user; +} + +- (void)updateAvatars { + + for (NSIndexPath *indexPath in self.avatarCollectionView.indexPathsForVisibleItems) + [self updateAvatarAtIndexPath:indexPath]; +} + +- (void)updateAvatarAtIndexPath:(NSIndexPath *)indexPath { + + MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath]; + [self updateAvatar:cell atIndexPath:indexPath]; +} + +- (void)updateAvatar:(MPAvatarCell *)cell atIndexPath:(NSIndexPath *)indexPath { + + CGFloat current = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:indexPath].center.x - + self.avatarCollectionView.contentOffset.x; + CGFloat max = self.avatarCollectionView.bounds.size.width; + cell.visibility = MAX(0, MIN( 1, 1 - ABS( current / (max / 2) - 1 ) )); +} + +- (void)registerObservers { + + if ([_notificationObservers count]) + return; + + Weakify(self); + _notificationObservers = @[ + [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationWillResignActiveNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + +// [self emergencyCloseAnimated:NO]; + self.userSelectionContainer.alpha = 0; + self.passwordSelectionContainer.alpha = 0; + }], + [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidBecomeActiveNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + + [self updateMode]; + [UIView animateWithDuration:1 animations:^{ + self.userSelectionContainer.alpha = 1; + self.passwordSelectionContainer.alpha = 1; + }]; + }], + [[NSNotificationCenter defaultCenter] + addObserverForName:MPSignedInNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + + self.mode = MPCombinedModePasswordSelection; + }], + [[NSNotificationCenter defaultCenter] + addObserverForName:MPSignedOutNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + + self.mode = MPCombinedModeUserSelection; + }], + [[NSNotificationCenter defaultCenter] + addObserverForName:UITextFieldTextDidChangeNotification object:self.entryField + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + + [self textFieldDidChange:note.object]; + }], + ]; +} + +- (void)removeObservers { + + for (id observer in _notificationObservers) + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + _notificationObservers = nil; +} + +- (void)needStoreObserved:(BOOL)observeStore { + + if (observeStore) { + Weakify(self); + + NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; + if (!_mocObserver && mainContext) + _mocObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + [self updateMode]; + }]; + if (!_storeObserver) + _storeObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:USMStoreDidChangeNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Strongify(self); + [self updateMode]; + }]; + } + if (!observeStore) { + if (_mocObserver) + [[NSNotificationCenter defaultCenter] removeObserver:_mocObserver]; + if (_storeObserver) + [[NSNotificationCenter defaultCenter] removeObserver:_storeObserver]; + } +} + +- (void)setUserIDs:(NSArray *)userIDs { + + _userIDs = userIDs; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self.avatarCollectionView reloadData]; + }]; +} + +- (void)setMode:(MPCombinedMode)mode { + + _mode = mode; + + [self updateMode]; +} + +- (void)updateMode { + + // Ensure we're on the main thread. + if (![NSThread isMainThread]) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self updateMode]; + }]; + return; + } + + self.userSelectionContainer.hidden = YES; + self.passwordSelectionContainer.hidden = YES; + + [self becomeFirstResponder]; + + switch (self.mode) { + case MPCombinedModeUserSelection: { + [[self navigationController] setNavigationBarHidden:YES animated:YES]; + self.userSelectionContainer.hidden = NO; + [self needStoreObserved:YES]; + + [self setActiveUserState:MPActiveUserStateNone animated:NO]; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + NSError *error = nil; + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; + fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ]; + NSArray *users = [context executeFetchRequest:fetchRequest error:&error]; + if (!users) { + err(@"Failed to load users: %@", error); + self.userIDs = nil; + } + + NSMutableArray *userIDs = [NSMutableArray arrayWithCapacity:[users count]]; + for (MPUserEntity *user in users) + [userIDs addObject:user.objectID]; + self.userIDs = userIDs; + }]; + + break; + } + case MPCombinedModePasswordSelection: { + [[self navigationController] setNavigationBarHidden:NO animated:YES]; + self.passwordSelectionContainer.hidden = NO; + [self needStoreObserved:NO]; + break; + } + } +} + +- (void)setActiveUserState:(MPActiveUserState)activeUserState { + + [self setActiveUserState:activeUserState animated:YES]; +} + +- (void)setActiveUserState:(MPActiveUserState)activeUserState animated:(BOOL)animated { + + _activeUserState = activeUserState; + _masterPasswordChoice = nil; + + [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ + // Set the entry container's contents. + switch (activeUserState) { + case MPActiveUserStateNone: { + for (NSUInteger item = 0; item < [self.avatarCollectionView numberOfItemsInSection:0]; ++item) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:0]; + [self.avatarCollectionView deselectItemAtIndexPath:indexPath animated:YES]; + + MPAvatarCell *avatarCell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath]; + avatarCell.mode = MPAvatarModeLowered; + } + break; + } + case MPActiveUserStateLogin: { + self.entryField.text = strl( @"Enter your master password:" ); + self.entryField.text = nil; + self.entryField.secureTextEntry = YES; + break; + } + case MPActiveUserStateUserName: { + self.entryLabel.text = strl( @"Enter your full name:" ); + self.entryField.text = nil; + self.entryField.secureTextEntry = NO; + break; + } + case MPActiveUserStateMasterPasswordChoice: { + self.entryLabel.text = strl( @"Choose your master password:" ); + self.entryField.text = nil; + self.entryField.secureTextEntry = YES; + break; + } + case MPActiveUserStateMasterPasswordConfirmation: { + _masterPasswordChoice = self.entryField.text; + self.entryLabel.text = strl( @"Confirm your master password:" ); + self.entryField.text = nil; + self.entryField.secureTextEntry = YES; + break; + } + } + + // Manage the random avatar for the new user if selected. + MPAvatarCell *selectedAvatar = [self selectedAvatar]; + if (selectedAvatar.avatar == MPAvatarAdd) { + selectedAvatar.avatar = arc4random() % MPAvatarCount; + } + else { + NSIndexPath *newUserIndexPath = [NSIndexPath indexPathForItem:[_userIDs count] inSection:0]; + MPAvatarCell *newUserAvatar = (MPAvatarCell *)[[self avatarCollectionView] cellForItemAtIndexPath:newUserIndexPath]; + newUserAvatar.avatar = MPAvatarAdd; + newUserAvatar.name = strl( @"New User" ); + } + + // Manage the entry container depending on whether a user is activate or not. + if (activeUserState == MPActiveUserStateNone) { + self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultHigh; + self.entryContainer.alpha = 0; + } + else { + self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultLow; + self.entryContainer.alpha = 1; + } + [self.avatarCollectionCenterConstraint apply]; + + // Toggle the keyboard. + if (activeUserState == MPActiveUserStateNone) + [self.entryField resignFirstResponder]; + } completion:^(BOOL finished) { + if (activeUserState != MPActiveUserStateNone) + [self.entryField becomeFirstResponder]; + }]; +} + +- (IBAction)doSignOut:(UIBarButtonItem *)sender { + + [[MPiOSAppDelegate get] signOutAnimated:YES]; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPPreferencesViewController.m b/MasterPassword/ObjC/iOS/MPPreferencesViewController.m index f9144b4e..39308bca 100644 --- a/MasterPassword/ObjC/iOS/MPPreferencesViewController.m +++ b/MasterPassword/ObjC/iOS/MPPreferencesViewController.m @@ -23,9 +23,9 @@ self.avatarTemplate.hidden = YES; - for (int a = 0; a < MPAvatarCount; ++a) { + for (NSUInteger a = 0; a < MPAvatarCount; ++a) { UIButton *avatar = [self.avatarTemplate clone]; - avatar.tag = a; + avatar.tag = (NSInteger)a; avatar.hidden = NO; avatar.center = CGPointMake( self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a, diff --git a/MasterPassword/ObjC/iOS/MPUnlockViewController.m b/MasterPassword/ObjC/iOS/MPUnlockViewController.m index 58143131..733f2269 100644 --- a/MasterPassword/ObjC/iOS/MPUnlockViewController.m +++ b/MasterPassword/ObjC/iOS/MPUnlockViewController.m @@ -46,10 +46,10 @@ [alert addSubview:alertAvatarScrollView]; CGPoint selectedOffset = CGPointZero; - for (int a = 0; a < MPAvatarCount; ++a) { + for (NSUInteger a = 0; a < MPAvatarCount; ++a) { UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView]; - avatar.tag = a; + avatar.tag = (NSInteger)a; avatar.hidden = NO; avatar.center = CGPointMake( (20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a, @@ -368,7 +368,7 @@ avatar.layer.shadowRadius = 20; avatar.layer.masksToBounds = NO; avatar.backgroundColor = [UIColor clearColor]; - avatar.tag = user.avatar; + avatar.tag = (NSInteger)user.avatar; [avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%lu", (unsigned long)user.avatar )] forState:UIControlStateNormal]; @@ -599,7 +599,7 @@ BOOL isTargeted = avatar == targetedAvatar; avatar.userInteractionEnabled = isTargeted; - avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1: 0.4; + avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1F: 0.4F; [self updateAvatarShadowColor:avatar isTargeted:isTargeted]; } recurse:NO]; @@ -649,7 +649,7 @@ - (void)initializeWordLabel:(UILabel *)wordLabel { - wordLabel.alpha = 0.05 + (random() % 35) / 100.0F; + wordLabel.alpha = 0.05F + (random() % 35) / 100.0F; wordLabel.text = (self.wordList)[(NSUInteger)random() % [self.wordList count]]; } diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 51dfe31a..2a7195b0 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -404,23 +404,23 @@ - (void)showGuide { - [self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self]; + //TODO [self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self]; MPCheckpoint( MPCheckpointShowGuide, nil ); } - (void)showSetup { - [self.navigationController performSegueWithIdentifier:@"MP_Setup" sender:self]; + //TODO [self.navigationController performSegueWithIdentifier:@"MP_Setup" sender:self]; MPCheckpoint( MPCheckpointShowSetup, nil ); } - (void)showReview { - MPCheckpoint( MPCheckpointReview, nil ); - [super showReview]; + + MPCheckpoint( MPCheckpointReview, nil ); } - (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController { diff --git a/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard index f34dfcd0..2ebbf993 100644 --- a/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/ObjC/iOS/MainStoryboard_iPhone.storyboard @@ -470,15 +470,15 @@ Your passwords will be AES-encrypted with your master password. - + - + - + 119-20:51:52 MPiOSAppDelegate.m:36 | INFO : Initializing TestFlight @@ -508,7 +508,7 @@ Your passwords will be AES-encrypted with your master password. - + @@ -1640,6 +1640,7 @@ You can use the words in the background for inspiration in finding a memorable m + @@ -1823,6 +1824,112 @@ You can use the words in the background for inspiration in finding a memorable m + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1873,11 +1980,11 @@ You can use the words in the background for inspiration in finding a memorable m - Enabling iCloud will keep all your iPhones, iPads and Macs nicely in-sync. Any site you add on this device will automatically appear on all your others as well. + Enabling iCloud will keep all your iPhones, iPads and Macs nicely in-sync. Any site you add on this device will automatically appear on all your others as well. Note that even without iCloud syncing, you can make your passwords available from any device by simply creating the same user on all your devices. Enabling iCloud is mainly benefitial to keep the list of sites you use in sync on all your devices. -Only site names and custom passwords are sent to iCloud. Passwords are encrypted with your master password and illegible by Apple or any interceptor. +Only site names and custom passwords are sent to iCloud. Passwords are encrypted with your master password and illegible by Apple or any interceptor. @@ -2359,7 +2466,7 @@ Only site names and custom passwords are sent to iCloud. Passwords are encrypte - + @@ -3062,8 +3169,8 @@ However, it means that anyone who finds your device unlocked can do the same. - - + + \ No newline at end of file diff --git a/MasterPassword/ObjC/iOS/MasterPassword-Info.plist b/MasterPassword/ObjC/iOS/MasterPassword-Info.plist index 1e2ee930..20246c83 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-Info.plist +++ b/MasterPassword/ObjC/iOS/MasterPassword-Info.plist @@ -78,7 +78,7 @@ SourceCodePro-ExtraLight.otf UIMainStoryboardFile - MainStoryboard_iPhone + Storyboard UIStatusBarHidden UIStatusBarStyle diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 2fabec83..f6171fbc 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 93D39262A8A97DB748213309 /* PearlEMail.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393BB973253D4BAAC84AA /* PearlEMail.m */; }; 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; }; 93D3932889B6B4206E66A6D6 /* PearlEMail.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */; }; + 93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; }; 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; }; 93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3956915634581E737B38C /* PearlNavigationController.m */; }; 93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D3942A356B639724157982 /* PearlOverlay.h */; }; @@ -22,10 +23,17 @@ 93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39C8AD8EAB747856B3A8C /* LLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3923B42DA2DA18F287092 /* LLModel.m */; }; + 93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; 93D39F8A9254177891F38705 /* MPSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A28369954D147E239BA /* MPSetupViewController.m */; }; DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; }; DA095E75172F4CD8001C948B /* MPLogsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */; }; + DA2CA4DD18D28859007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4D918D28859007798F8 /* NSArray+Pearl.m */; }; + DA2CA4DE18D28859007798F8 /* NSArray+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4DA18D28859007798F8 /* NSArray+Pearl.h */; }; + DA2CA4DF18D28859007798F8 /* NSTimer+PearlBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4DB18D28859007798F8 /* NSTimer+PearlBlock.m */; }; + DA2CA4E018D28859007798F8 /* NSTimer+PearlBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4DC18D28859007798F8 /* NSTimer+PearlBlock.h */; }; + DA2CA4E418D28866007798F8 /* NSLayoutConstraint+PearlUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E218D28866007798F8 /* NSLayoutConstraint+PearlUIKit.h */; }; + DA2CA4E618D2AC10007798F8 /* NSLayoutConstraint+PearlUIKit.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E518D2AC10007798F8 /* NSLayoutConstraint+PearlUIKit.m */; }; DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; }; DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; }; DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; }; @@ -35,6 +43,7 @@ DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; }; DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; }; DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; }; + DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */; }; DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA44260A1557D9E40052177D /* libUbiquityStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libUbiquityStoreManager.a */; }; DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; }; @@ -58,6 +67,8 @@ DA70EC801811B13C00F65DB2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */; }; DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA829E51159847E0002417D3 /* libFontReplacer.a */; }; + DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8118D4CFBF00106317 /* avatar-add@2x.png */; }; + DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8218D4CFBF00106317 /* avatar-add.png */; }; DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA945C8617E3F3FD0053236B /* Images.xcassets */; }; DA95D5F214DF0B2C008D1B94 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */; }; DA9B51551895D79E009D2A0B /* gittip.png in Resources */ = {isa = PBXBuildFile; fileRef = DA9B51541895D79E009D2A0B /* gittip.png */; }; @@ -489,6 +500,7 @@ 93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = ""; }; 93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = ""; }; 93D3923B42DA2DA18F287092 /* LLModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLModel.m; sourceTree = ""; }; + 93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = ""; }; 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = ""; }; 93D393BB973253D4BAAC84AA /* PearlEMail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlEMail.m; sourceTree = ""; }; 93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; }; @@ -503,12 +515,21 @@ 93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = ""; }; 93D39A3CC4D8330831FC8CB4 /* LLToggleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLToggleViewController.h; sourceTree = ""; }; 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = ""; }; + 93D39B381350802A194BF332 /* MPAvatarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAvatarCell.m; sourceTree = ""; }; 93D39BA6C5CB452973918B7D /* LLButtonView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLButtonView.h; sourceTree = ""; }; 93D39BF6BCBDFFE844E7D34C /* LLButtonView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLButtonView.m; sourceTree = ""; }; 93D39C8E26B06F01566785B7 /* LLToggleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLToggleViewController.m; sourceTree = ""; }; + 93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCombinedViewController.h; sourceTree = ""; }; + 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAvatarCell.h; sourceTree = ""; }; 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = ""; }; 93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = ""; }; DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + DA2CA4D918D28859007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = ""; }; + DA2CA4DA18D28859007798F8 /* NSArray+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Pearl.h"; sourceTree = ""; }; + DA2CA4DB18D28859007798F8 /* NSTimer+PearlBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+PearlBlock.m"; sourceTree = ""; }; + DA2CA4DC18D28859007798F8 /* NSTimer+PearlBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+PearlBlock.h"; sourceTree = ""; }; + DA2CA4E218D28866007798F8 /* NSLayoutConstraint+PearlUIKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSLayoutConstraint+PearlUIKit.h"; sourceTree = ""; }; + DA2CA4E518D2AC10007798F8 /* NSLayoutConstraint+PearlUIKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSLayoutConstraint+PearlUIKit.m"; sourceTree = ""; }; DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = ""; }; DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = ""; }; DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = ""; }; @@ -520,6 +541,7 @@ DA340E9F17CD830E00712B77 /* TestFlight+ManualSessions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TestFlight+ManualSessions.h"; sourceTree = ""; }; DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = ""; }; DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = ""; }; + DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = ""; }; DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; DA4425CB1557BED40052177D /* libUbiquityStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUbiquityStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; }; DA5A09DD171A70E4005284AB /* play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play.png; sourceTree = ""; }; @@ -538,6 +560,8 @@ DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; DA829E51159847E0002417D3 /* libFontReplacer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFontReplacer.a; sourceTree = BUILT_PRODUCTS_DIR; }; + DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = ""; }; + DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = ""; }; DA945C8617E3F3FD0053236B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; DA9B51541895D79E009D2A0B /* gittip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gittip.png; sourceTree = ""; }; @@ -1854,6 +1878,8 @@ DABD366B1711E29400CF925C /* Avatars */ = { isa = PBXGroup; children = ( + DA854C8118D4CFBF00106317 /* avatar-add@2x.png */, + DA854C8218D4CFBF00106317 /* avatar-add.png */, DABD366C1711E29400CF925C /* avatar-0.png */, DABD366D1711E29400CF925C /* avatar-0@2x.png */, DABD366E1711E29400CF925C /* avatar-1.png */, @@ -2488,6 +2514,11 @@ 93D39730673227EFF6DEFF19 /* MPSetupViewController.h */, 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */, 93D391943675426839501BB8 /* MPLogsViewController.h */, + 93D393310223DDB35218467A /* MPCombinedViewController.m */, + 93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */, + DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */, + 93D39B381350802A194BF332 /* MPAvatarCell.m */, + 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */, ); path = iOS; sourceTree = ""; @@ -2892,6 +2923,10 @@ DAFE45D715039823003ABA7C /* Pearl */ = { isa = PBXGroup; children = ( + DA2CA4D918D28859007798F8 /* NSArray+Pearl.m */, + DA2CA4DA18D28859007798F8 /* NSArray+Pearl.h */, + DA2CA4DB18D28859007798F8 /* NSTimer+PearlBlock.m */, + DA2CA4DC18D28859007798F8 /* NSTimer+PearlBlock.h */, DA3509FC15F101A500C14A8E /* PearlQueue.h */, DA3509FD15F101A500C14A8E /* PearlQueue.m */, 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */, @@ -2973,6 +3008,8 @@ DAFE460715039823003ABA7C /* Pearl-UIKit */ = { isa = PBXGroup; children = ( + DA2CA4E518D2AC10007798F8 /* NSLayoutConstraint+PearlUIKit.m */, + DA2CA4E218D28866007798F8 /* NSLayoutConstraint+PearlUIKit.h */, 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */, 93D393BB973253D4BAAC84AA /* PearlEMail.m */, DAFE4A63150399FF003ABA8F /* UIScrollView+PearlFlashingIndicators.h */, @@ -3102,6 +3139,7 @@ DAEB935A18AA537D000490CC /* idea.h in Headers */, DAEB933A18AA537D000490CC /* sysendian.h in Headers */, DAFE4A2415039824003ABA7C /* PearlInfoPlist.h in Headers */, + DA2CA4E418D28866007798F8 /* NSLayoutConstraint+PearlUIKit.h in Headers */, DAFE4A2615039824003ABA7C /* PearlLogger.h in Headers */, DAFE4A2815039824003ABA7C /* PearlMathUtils.h in Headers */, DAEB934418AA537D000490CC /* camellia.h in Headers */, @@ -3113,6 +3151,7 @@ DAEB935318AA537D000490CC /* ec.h in Headers */, DAEB937818AA537D000490CC /* ssl3.h in Headers */, DAEB935E18AA537D000490CC /* md4.h in Headers */, + DA2CA4E018D28859007798F8 /* NSTimer+PearlBlock.h in Headers */, DAEB933518AA537D000490CC /* memlimit.h in Headers */, DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */, DAEB937318AA537D000490CC /* seed.h in Headers */, @@ -3189,6 +3228,7 @@ DAEB936C18AA537D000490CC /* pqueue.h in Headers */, DAEB937B18AA537D000490CC /* tls1.h in Headers */, DAEB933818AA537D000490CC /* scryptenc_cpuperf.h in Headers */, + DA2CA4DE18D28859007798F8 /* NSArray+Pearl.h in Headers */, 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */, DAEB935418AA537D000490CC /* ecdh.h in Headers */, DAEB937D18AA537D000490CC /* txt_db.h in Headers */, @@ -3558,6 +3598,7 @@ DABD38FF1711E29700CF925C /* ui_navbar_container.png in Resources */, DABD39001711E29700CF925C /* ui_navbar_container@2x.png in Resources */, DABD39011711E29700CF925C /* ui_panel_container.png in Resources */, + DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */, DABD39021711E29700CF925C /* ui_panel_container@2x.png in Resources */, DABD39031711E29700CF925C /* ui_panel_display.png in Resources */, DABD39041711E29700CF925C /* ui_panel_display@2x.png in Resources */, @@ -3678,6 +3719,7 @@ DABD3B9D1711E29800CF925C /* social-twitter.png in Resources */, DABD3B9E1711E29800CF925C /* social-twitter@2x.png in Resources */, DABD3C191711E2DC00CF925C /* MPElementListCellView.xib in Resources */, + DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */, DABD3C221711E2DC00CF925C /* MainStoryboard_iPhone.storyboard in Resources */, DABD3C241711E2DC00CF925C /* MasterPassword.entitlements in Resources */, DABD3C251711E2DC00CF925C /* Settings.bundle in Resources */, @@ -3688,6 +3730,7 @@ DABD3FCF1714F45C00CF925C /* identity@2x.png in Resources */, DADEF4151810D2940052CA3E /* love-lyndir.button.grey@2x.png in Resources */, DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */, + DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */, DA5A09DF171A70E4005284AB /* play.png in Resources */, DA5A09E0171A70E4005284AB /* play@2x.png in Resources */, DA5A09EA171BB0F7005284AB /* unlocked.png in Resources */, @@ -3774,6 +3817,8 @@ DABD3C271711E2DC00CF925C /* main.m in Sources */, 93D39F8A9254177891F38705 /* MPSetupViewController.m in Sources */, DA095E75172F4CD8001C948B /* MPLogsViewController.m in Sources */, + 93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */, + 93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3845,9 +3890,11 @@ DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */, DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */, DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */, + DA2CA4DD18D28859007798F8 /* NSArray+Pearl.m in Sources */, DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */, DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */, DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */, + DA2CA4E618D2AC10007798F8 /* NSLayoutConstraint+PearlUIKit.m in Sources */, 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */, 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */, 93D39262A8A97DB748213309 /* PearlEMail.m in Sources */, @@ -3855,6 +3902,7 @@ 93D3922A53E41A54832E90D9 /* PearlOverlay.m in Sources */, 93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */, 93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */, + DA2CA4DF18D28859007798F8 /* NSTimer+PearlBlock.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3980,7 +4028,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_RECEIVER_WEAK = NO; @@ -4025,7 +4073,7 @@ GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_MISSING_PARENTHESES = YES; GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; - GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_SIGN_COMPARE = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = NO; @@ -4067,7 +4115,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_RECEIVER_WEAK = NO; @@ -4116,7 +4164,7 @@ GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_MISSING_PARENTHESES = YES; GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; - GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_SIGN_COMPARE = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; @@ -4225,7 +4273,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_OBJC_RECEIVER_WEAK = NO; @@ -4273,7 +4321,7 @@ GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; GCC_WARN_MISSING_PARENTHESES = YES; GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; - GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_SIGN_COMPARE = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard new file mode 100644 index 00000000..3c786956 --- /dev/null +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -0,0 +1,923 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MasterPassword/Resources/Media/Avatars/avatar-add.png b/MasterPassword/Resources/Media/Avatars/avatar-add.png new file mode 100644 index 0000000000000000000000000000000000000000..be68d16ec5b63f08d3514742f8549df179dee8fe GIT binary patch literal 7770 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 literal 0 HcmV?d00001 diff --git a/MasterPassword/Resources/Media/Avatars/avatar-add@2x.png b/MasterPassword/Resources/Media/Avatars/avatar-add@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3e91b6dc34bb7c2852d044a2ec508639c3695b32 GIT binary patch literal 16583 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