diff --git a/External/FontReplacer b/External/FontReplacer deleted file mode 160000 index 4e3dea08..00000000 --- a/External/FontReplacer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4e3dea08702906fc5d51c8b75dda5da29c545a74 diff --git a/External/Pearl b/External/Pearl index b50115c7..d462ada8 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit b50115c767e7c0d412099ae169891dbb625b8c64 +Subproject commit d462ada876939cab875e08736b1efc7af065848d diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index 03a2ed86..59f92e58 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -31,6 +31,21 @@ return 0; } +- (NSString *)description { + + return strf( @"<%@: version=%d>", NSStringFromClass( [self class] ), self.version ); +} + +- (BOOL)isEqual:(id)other { + + if (other == self) + return YES; + if (!other || ![other conformsToProtocol:@protocol(MPAlgorithm)]) + return NO; + + return [(id)other version] == [self version]; +} + - (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc { NSError *error = nil; @@ -250,23 +265,24 @@ uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length); NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof(ncounter)]; NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)]; - trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], [nameLengthBytes encodeHex], name, [counterBytes encodeHex]); + trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %@ | %@ | %@)", [key.keyData encodeBase64], + [nameLengthBytes encodeHex], name, [counterBytes encodeHex]); NSData *seed = [[NSData dataByConcatenatingDatas: [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], - nameLengthBytes, - [name dataUsingEncoding:NSUTF8StringEncoding], - counterBytes, - nil] + nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding], + counterBytes, nil] hmacWith:PearlHashSHA256 key:key.keyData]; trc(@"seed is: %@", [seed encodeBase64]); const char *seedBytes = seed.bytes; // Determine the cipher from the first seed byte. NSAssert([seed length], @"Missing seed."); - NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] - valueForKey:[self nameOfType:type]]; + NSString *typeClass = [self classNameOfType:type]; + NSString *typeName = [self nameOfType:type]; + id classCiphers = [MPTypes_ciphers valueForKey:typeClass]; + NSArray *typeCiphers = [classCiphers valueForKey:typeName]; NSString *cipher = typeCiphers[htons(seedBytes[0]) % [typeCiphers count]]; - trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); + trc(@"type %@, ciphers: %@, selected: %@", typeName, typeCiphers, cipher); // Encode the content, character by character, using subsequent seed bytes and the cipher. NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher."); @@ -275,8 +291,7 @@ uint16_t keyByte = htons(seedBytes[c + 1]); NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )]; NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass]; - NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], - 1 )]; + NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )]; trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character); [content appendString:character]; diff --git a/MasterPassword/ObjC/MPAppDelegate_Key.m b/MasterPassword/ObjC/MPAppDelegate_Key.m index 6c6e2737..37fcdf58 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Key.m +++ b/MasterPassword/ObjC/MPAppDelegate_Key.m @@ -168,7 +168,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { MPKey *recoverKey = newKey; #ifdef PEARL_UIKIT - PearlOverlay *activityOverlay = [PearlOverlay showOverlayWithTitle:PearlString( @"Migrating %ld sites...", (long)[user.elements count] )]; + PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...", (long)[user.elements count] )]; #endif for (MPElementEntity *element in user.elements) { diff --git a/MasterPassword/ObjC/MPAppDelegate_Shared.m b/MasterPassword/ObjC/MPAppDelegate_Shared.m index 5b14c946..9121422a 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Shared.m +++ b/MasterPassword/ObjC/MPAppDelegate_Shared.m @@ -30,11 +30,12 @@ - (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc { - if (!self.activeUserOID || !moc) + NSManagedObjectID *activeUserOID = self.activeUserOID; + if (!activeUserOID || !moc) return nil; NSError *error; - MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:self.activeUserOID error:&error]; + MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:activeUserOID error:&error]; if (!activeUser) { [self signOutAnimated:YES]; err(@"Failed to retrieve active user: %@", error); diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.h b/MasterPassword/ObjC/MPAppDelegate_Store.h index b2ba5269..5f701e16 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.h +++ b/MasterPassword/ObjC/MPAppDelegate_Store.h @@ -30,7 +30,7 @@ typedef enum { /** @param completion The block to execute after adding the element, executed from the main thread with the new element in the main MOC. */ - (void)addElementNamed:(NSString *)siteName completion:(void (^)(MPElementEntity *element))completion; -- (MPElementEntity *)changeElement:(MPElementEntity *)element inContext:(NSManagedObjectContext *)context toType:(MPElementType)type; +- (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type; - (MPImportResult)importSites:(NSString *)importedSitesString askImportPassword:(NSString *(^)(NSString *userName))importPassword askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword; diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index cf4703f2..6caac068 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -32,7 +32,8 @@ typedef NS_ENUM(NSInteger, MPMigrationLevelCloudStore) { }; @implementation MPAppDelegate_Shared(Store) - PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext); + PearlAssociatedObjectProperty(id, SaveObserver, saveObserver); +PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext); PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext); @@ -50,7 +51,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, + (BOOL)managedObjectContextForMainThreadPerformBlock:(void (^)(NSManagedObjectContext *mainContext))mocBlock { - NSManagedObjectContext *mainManagedObjectContext = [self managedObjectContextForMainThreadIfReady]; + NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; if (!mainManagedObjectContext) return NO; @@ -63,7 +64,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, + (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void (^)(NSManagedObjectContext *mainContext))mocBlock { - NSManagedObjectContext *mainManagedObjectContext = [self managedObjectContextForMainThreadIfReady]; + NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady]; if (!mainManagedObjectContext) return NO; @@ -318,6 +319,11 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, [moc saveToStore]; [moc reset]; + if (self.saveObserver) { + [[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver]; + self.saveObserver = nil; + } + self.privateManagedObjectContext = nil; self.mainManagedObjectContext = nil; }]; @@ -334,8 +340,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, } ); // Create our contexts. - NSManagedObjectContext - *privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + NSManagedObjectContext *privateManagedObjectContext = + [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [privateManagedObjectContext performBlockAndWait:^{ privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; privateManagedObjectContext.persistentStoreCoordinator = coordinator; @@ -358,6 +364,16 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, NSManagedObjectContext *mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; mainManagedObjectContext.parentContext = privateManagedObjectContext; + if (self.saveObserver) + [[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver]; + self.saveObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:NSManagedObjectContextDidSaveNotification object:privateManagedObjectContext queue:nil usingBlock: + ^(NSNotification *note) { + [mainManagedObjectContext performBlock:^{ + [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note]; + }]; + }]; + self.privateManagedObjectContext = privateManagedObjectContext; self.mainManagedObjectContext = mainManagedObjectContext; } @@ -386,12 +402,12 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPUserEntity *activeUser = [self activeUserInContext:context]; NSAssert(activeUser, @"Missing user."); - if (!activeUser) + if (!activeUser) { + completion( nil ); return; + } MPElementType type = activeUser.defaultType; - if (!type) - type = activeUser.defaultType = MPElementTypeGeneratedLong; NSString *typeEntityClassName = [MPAlgorithmDefault classNameOfType:type]; MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityClassName @@ -408,18 +424,19 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error]) err(@"Failed to obtain a permanent object ID after creating new element: %@", error); - NSManagedObjectID *elementOID = [element objectID]; - dispatch_async( dispatch_get_main_queue(), ^{ - completion( - (MPElementEntity *)[[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady] objectRegisteredForID:elementOID] ); - } ); + completion( element ); }]; } -- (MPElementEntity *)changeElement:(MPElementEntity *)element inContext:(NSManagedObjectContext *)context toType:(MPElementType)type { +- (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type { - if ([element.algorithm classOfType:type] == element.typeClass) + if (element.type == type) + return element; + + if ([element.algorithm classOfType:type] == element.typeClass) { element.type = type; + [context saveToStore]; + } else { // Type requires a different class of element. Recreate the element. @@ -435,14 +452,13 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, newElement.loginName = element.loginName; [context deleteObject:element]; - // TODO: Dodgy... we're not saving consistently here. - // Either we should save regardless and change the method signature to saveInContext: or not save at all. [context saveToStore]; NSError *error; if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error]) err(@"Failed to obtain a permanent object ID after changing object type: %@", error); + [[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID]; element = newElement; } @@ -728,7 +744,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, [export appendFormat:@"%@ %8ld %8s %20s\t%@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses, [PearlString( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content - ? content: @""]; + ? content: @""]; } MPCheckpoint( MPCheckpointSitesExported, @{ diff --git a/MasterPassword/ObjC/MPEntities.m b/MasterPassword/ObjC/MPEntities.m index e46f60b5..1efc3208 100644 --- a/MasterPassword/ObjC/MPEntities.m +++ b/MasterPassword/ObjC/MPEntities.m @@ -8,13 +8,14 @@ #import "MPEntities.h" #import "MPAppDelegate_Shared.h" +#import "MPAppDelegate_Store.h" @implementation NSManagedObjectContext(MP) - (BOOL)saveToStore { __block BOOL success = YES; - if ([self hasChanges]) + if ([self hasChanges]) { [self performBlockAndWait:^{ @try { NSError *error = nil; @@ -26,6 +27,7 @@ err(@"While saving: %@", exception); } }]; + } return success && (!self.parentContext || [self.parentContext saveToStore]); } @@ -42,6 +44,14 @@ type = [self.user defaultType]; if (!type || type == (MPElementType)NSNotFound) type = MPElementTypeGeneratedLong; + if (![self isKindOfClass:[self.algorithm classOfType:type]]) { +// NSAssert(NO, @"This object's class does not support the type: %lu", (long)type); + for (MPElementType aType = type; type != (aType = [self.algorithm nextType:aType]);) + if ([self isKindOfClass:[self.algorithm classOfType:aType]]) { + err(@"Invalid type for: %@, type: %lu. Will use %lu instead.", self.name, (long)type, (long)aType); + return aType; + } + } return type; } @@ -199,7 +209,7 @@ - (MPElementType)defaultType { - return (MPElementType)[self.defaultType_ unsignedIntegerValue]; + return IfElse((MPElementType)[self.defaultType_ unsignedIntegerValue], MPElementTypeGeneratedLong); } - (void)setDefaultType:(MPElementType)aDefaultType { diff --git a/MasterPassword/ObjC/Mac/MPElementCollectionView.m b/MasterPassword/ObjC/Mac/MPElementCollectionView.m index d90c3c01..498e71f8 100644 --- a/MasterPassword/ObjC/Mac/MPElementCollectionView.m +++ b/MasterPassword/ObjC/Mac/MPElementCollectionView.m @@ -113,9 +113,8 @@ // "Next type" button. [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPElementEntity *element = [self.representedObject entityInContext:context]; - element = [[MPMacAppDelegate get] changeElement:element inContext:context + element = [[MPMacAppDelegate get] changeElement:element saveInContext:context toType:[element.algorithm nextType:element.type]]; - [context saveToStore]; self.representedObject = [[MPElementModel alloc] initWithEntity:element]; }]; @@ -131,9 +130,8 @@ // "Previous type" button. [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPElementEntity *element = [self.representedObject entityInContext:context]; - element = [[MPMacAppDelegate get] changeElement:element inContext:context + element = [[MPMacAppDelegate get] changeElement:element saveInContext:context toType:[element.algorithm previousType:element.type]]; - [context saveToStore]; self.representedObject = [[MPElementModel alloc] initWithEntity:element]; }]; diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m index bf450bfc..0b92b5ac 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.m +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.m @@ -182,7 +182,7 @@ // "Create" button. [[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element) { if (element) - [self updateElements]; + PearlMainQueue( ^{ [self updateElements]; } ); }]; break; } diff --git a/MasterPassword/ObjC/iOS/MPAvatarCell.h b/MasterPassword/ObjC/iOS/MPAvatarCell.h index f1d0ac9e..b3429acb 100644 --- a/MasterPassword/ObjC/iOS/MPAvatarCell.h +++ b/MasterPassword/ObjC/iOS/MPAvatarCell.h @@ -37,6 +37,7 @@ typedef NS_ENUM(NSUInteger, MPAvatarMode) { @property (assign, nonatomic) MPAvatarMode mode; @property (assign, nonatomic) CGFloat visibility; @property (assign, nonatomic) BOOL spinnerActive; +@property (assign, nonatomic, readonly) BOOL newUser; + (NSString *)reuseIdentifier; diff --git a/MasterPassword/ObjC/iOS/MPAvatarCell.m b/MasterPassword/ObjC/iOS/MPAvatarCell.m index f9dbb213..063b4633 100644 --- a/MasterPassword/ObjC/iOS/MPAvatarCell.m +++ b/MasterPassword/ObjC/iOS/MPAvatarCell.m @@ -17,6 +17,7 @@ // #import "MPAvatarCell.h" +#import "MPPasswordLargeCell.h" const long MPAvatarAdd = 10000; @@ -34,6 +35,7 @@ const long MPAvatarAdd = 10000; @end @implementation MPAvatarCell { + CAAnimationGroup *_targetedShadowAnimation; } + (NSString *)reuseIdentifier { @@ -47,6 +49,8 @@ const long MPAvatarAdd = 10000; [super awakeFromNib]; + self.alpha = 0; + self.nameContainer.layer.cornerRadius = 5; self.avatarImageView.hidden = NO; @@ -73,15 +77,21 @@ const long MPAvatarAdd = 10000; pulseShadowOpacityAnimation.autoreverses = YES; pulseShadowOpacityAnimation.repeatCount = MAXFLOAT; - CAAnimationGroup *group = [CAAnimationGroup new]; - group.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ]; - group.duration = MAXFLOAT; - [self.avatarImageView.layer addAnimation:group forKey:@"targetedShadow"]; + _targetedShadowAnimation = [CAAnimationGroup new]; + _targetedShadowAnimation.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ]; + _targetedShadowAnimation.duration = MAXFLOAT; self.avatarImageView.layer.shadowColor = [UIColor whiteColor].CGColor; self.avatarImageView.layer.shadowOffset = CGSizeZero; +} +- (void)prepareForReuse { + + [super prepareForReuse]; + + _newUser = NO; [self setVisibility:0 animated:NO]; [self setMode:MPAvatarModeLowered animated:NO]; + [self setSpinnerActive:NO animated:NO]; } - (void)dealloc { @@ -95,8 +105,11 @@ const long MPAvatarAdd = 10000; _avatar = avatar; - if (avatar == MPAvatarAdd) + if (avatar == MPAvatarAdd) { self.avatarImageView.image = [UIImage imageNamed:@"avatar-add"]; + self.name = strl( @"New User" ); + _newUser = YES; + } else self.avatarImageView.image = [UIImage imageNamed:strf( @"avatar-%ld", avatar )]; } @@ -151,6 +164,8 @@ const long MPAvatarAdd = 10000; - (void)setSpinnerActive:(BOOL)spinnerActive animated:(BOOL)animated { + if (_spinnerActive == spinnerActive) + return; _spinnerActive = spinnerActive; CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; @@ -180,10 +195,20 @@ const long MPAvatarAdd = 10000; [UIView animateWithDuration:animated? 0.2f: 0 animations:^{ self.avatarImageView.transform = CGAffineTransformIdentity; }]; - [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ + [UIView animateWithDuration:animated? 0.3f: 0 delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{ + self.alpha = 1; + + if (self.newUser) { + if (self.mode == MPAvatarModeLowered) + self.avatar = MPAvatarAdd; + else if (self.avatar == MPAvatarAdd) + self.avatar = arc4random() % MPAvatarCount; + } + switch (self.mode) { case MPAvatarModeLowered: { + self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height; self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultLow; self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow; @@ -235,14 +260,20 @@ const long MPAvatarAdd = 10000; self.nameContainer.alpha = 0; self.nameContainer.backgroundColor = [UIColor blackColor]; self.avatarImageView.alpha = 1; - self.avatarImageView.layer.shadowOpacity = 0; break; } } [self.avatarSizeConstraint apply]; + [self.avatarRaisedConstraint apply]; [self.avatarToTopConstraint apply]; [self.nameToCenterConstraint apply]; + // Avatar minimized. + if (self.mode == MPAvatarModeRaisedAndMinimized) + [self.avatarImageView.layer removeAllAnimations]; + else if (![self.avatarImageView.layer animationForKey:@"targetedShadow"]) + [self.avatarImageView.layer addAnimation:_targetedShadowAnimation forKey:@"targetedShadow"]; + // Avatar selection and spinner. if (self.mode != MPAvatarModeRaisedAndMinimized && (self.selected || self.highlighted) && !self.spinnerActive) self.avatarImageView.backgroundColor = self.avatarImageView.tintColor; @@ -250,7 +281,7 @@ const long MPAvatarAdd = 10000; self.avatarImageView.backgroundColor = [UIColor clearColor]; self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2; self.spinner.alpha = self.spinnerActive? 1: 0; - }]; + } completion:nil]; } @end diff --git a/MasterPassword/ObjC/iOS/PearlUIView.h b/MasterPassword/ObjC/iOS/MPCell.h similarity index 75% rename from MasterPassword/ObjC/iOS/PearlUIView.h rename to MasterPassword/ObjC/iOS/MPCell.h index 96db6438..5a518452 100644 --- a/MasterPassword/ObjC/iOS/PearlUIView.h +++ b/MasterPassword/ObjC/iOS/MPCell.h @@ -9,17 +9,15 @@ */ // -// PearlUIView.h -// PearlUIView +// MPCell.h +// MPCell // -// Created by lhunath on 2014-03-17. +// Created by lhunath on 2014-03-27. // Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. // #import -@interface PearlUIView : UIView - -@property(assign, nonatomic) BOOL ignoreTouches; +@interface MPCell : UICollectionViewCell @end diff --git a/MasterPassword/ObjC/iOS/PearlUINavigationBar.h b/MasterPassword/ObjC/iOS/MPCell.m similarity index 61% rename from MasterPassword/ObjC/iOS/PearlUINavigationBar.h rename to MasterPassword/ObjC/iOS/MPCell.m index 3bb15eb4..e18e16ba 100644 --- a/MasterPassword/ObjC/iOS/PearlUINavigationBar.h +++ b/MasterPassword/ObjC/iOS/MPCell.m @@ -9,18 +9,16 @@ */ // -// PearlUINavigationBar.h -// PearlUINavigationBar +// MPCell.h +// MPCell // -// Created by lhunath on 2014-03-17. +// Created by lhunath on 2014-03-27. // Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. // -#import +#import "MPCell.h" -@interface PearlUINavigationBar : UINavigationBar - -@property (assign, nonatomic) BOOL ignoreTouches; -@property (assign, nonatomic) BOOL invisible; +@implementation MPCell { +} @end diff --git a/MasterPassword/ObjC/iOS/MPCombinedViewController.m b/MasterPassword/ObjC/iOS/MPCombinedViewController.m index b365e918..2757659b 100644 --- a/MasterPassword/ObjC/iOS/MPCombinedViewController.m +++ b/MasterPassword/ObjC/iOS/MPCombinedViewController.m @@ -89,19 +89,18 @@ [self becomeFirstResponder]; - [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ switch (self.mode) { case MPCombinedModeUserSelection: { - [self.usersVC setActive:YES animated:NO]; - [self.passwordsVC setActive:NO animated:NO]; + [self.usersVC setActive:YES animated:animated]; + [self.passwordsVC setActive:NO animated:animated]; // MPUsersViewController *usersVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MPUsersViewController"]; // [self setViewControllers:@[ usersVC ] direction:UIPageViewControllerNavigationDirectionReverse // animated:animated completion:nil]; break; } case MPCombinedModePasswordSelection: { - [self.usersVC setActive:NO animated:NO]; - [self.passwordsVC setActive:YES animated:NO]; + [self.usersVC setActive:NO animated:animated]; + [self.passwordsVC setActive:YES animated:animated]; // MPPasswordsViewController *passwordsVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MPPasswordsViewController"]; // [self setViewControllers:@[ passwordsVC ] direction:UIPageViewControllerNavigationDirectionForward // animated:animated completion:nil]; @@ -110,7 +109,6 @@ } [self.passwordsTopConstraint apply]; - }]; } #pragma mark - Private diff --git a/MasterPassword/ObjC/iOS/MPElementListAllViewController.m b/MasterPassword/ObjC/iOS/MPElementListAllViewController.m index 381f732f..2cf9243b 100644 --- a/MasterPassword/ObjC/iOS/MPElementListAllViewController.m +++ b/MasterPassword/ObjC/iOS/MPElementListAllViewController.m @@ -66,10 +66,11 @@ __weak MPElementListAllViewController *wSelf = self; [[MPiOSAppDelegate get] addElementNamed:[alert textFieldAtIndex:0].text completion:^(MPElementEntity *element) { - if (element) { - [wSelf.delegate didSelectElement:element]; - [wSelf close:nil]; - } + if (element) + PearlMainQueue( ^{ + [wSelf.delegate didSelectElement:element]; + [wSelf close:nil]; + } ); }]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonOkay, nil]; @@ -85,7 +86,7 @@ if (buttonIndex == [alert cancelButtonIndex]) return; - PearlOverlay *activity = [PearlOverlay showOverlayWithTitle:@"Upgrading Sites"]; + PearlOverlay *activity = [PearlOverlay showProgressOverlayWithTitle:@"Upgrading Sites"]; [self performUpgradeAllWithCompletion:^(BOOL success, NSDictionary *changes) { dispatch_async( dispatch_get_main_queue(), ^{ [self showUpgradeChanges:changes]; diff --git a/MasterPassword/ObjC/iOS/MPElementListSearchController.m b/MasterPassword/ObjC/iOS/MPElementListSearchController.m index 1572a32e..58b995fe 100644 --- a/MasterPassword/ObjC/iOS/MPElementListSearchController.m +++ b/MasterPassword/ObjC/iOS/MPElementListSearchController.m @@ -193,7 +193,7 @@ NSString *query = [self.searchDisplayController.searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; cell.textLabel.text = query; cell.detailTextLabel.text = PearlString( @"New site: %@", - [MPAlgorithmDefault shortNameOfType:[[[MPiOSAppDelegate get] activeUserForMainThread] defaultType]] ); + [MPAlgorithmDefault shortNameOfType:[[MPiOSAppDelegate get] activeUserForMainThread].defaultType] ); } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { @@ -220,7 +220,9 @@ __weak MPElementListController *wSelf = self; [[MPiOSAppDelegate get] addElementNamed:siteName completion:^(MPElementEntity *element) { if (element) - [wSelf.delegate didSelectElement:element]; + PearlMainQueue( ^{ + [wSelf.delegate didSelectElement:element]; + } ); }]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; } diff --git a/MasterPassword/ObjC/iOS/MPLogsViewController.m b/MasterPassword/ObjC/iOS/MPLogsViewController.m index 81bb8c04..3a926662 100644 --- a/MasterPassword/ObjC/iOS/MPLogsViewController.m +++ b/MasterPassword/ObjC/iOS/MPLogsViewController.m @@ -62,7 +62,7 @@ if (buttonIndex_ == alert.cancelButtonIndex) return; - _switchCloudStoreProgress = [PearlOverlay showOverlayWithTitle:@"Enumerating Stores"]; + _switchCloudStoreProgress = [PearlOverlay showProgressOverlayWithTitle:@"Enumerating Stores"]; dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{ [self switchCloudStore]; } ); diff --git a/MasterPassword/ObjC/iOS/MPMainViewController.m b/MasterPassword/ObjC/iOS/MPMainViewController.m index 88dfeed9..2637e252 100644 --- a/MasterPassword/ObjC/iOS/MPMainViewController.m +++ b/MasterPassword/ObjC/iOS/MPMainViewController.m @@ -776,7 +776,7 @@ @"If you continue, the password for this site will change. " @"You will need to update your account's old password to the new one." do:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) { - _activeElementOID = [[MPiOSAppDelegate get] changeElement:activeElement inContext:context + _activeElementOID = [[MPiOSAppDelegate get] changeElement:activeElement saveInContext:context toType:type].objectID; return YES; }]; diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.h b/MasterPassword/ObjC/iOS/MPPasswordCell.h index 8d1116c4..1bafd03a 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.h +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.h @@ -18,19 +18,17 @@ #import #import "MPEntities.h" +#import "MPCell.h" -@interface MPPasswordCell : UICollectionViewCell +@interface MPPasswordCell : MPCell -@property(nonatomic, copy) NSString *transientSite; -@property(strong, nonatomic) IBOutlet UITextField *contentField; - -+ (NSString *)reuseIdentifier; - -- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context; -+ (NSString *)reuseIdentifierForElement:(MPElementEntity *)entity; -- (void)setElement:(MPElementEntity *)element; +@property(strong, nonatomic) IBOutlet UILabel *nameLabel; +@property(strong, nonatomic) IBOutlet UIButton *loginButton; +/** Populate our UI to reflect the current state. */ - (void)updateAnimated:(BOOL)animated; -- (void)populateWithElement:(MPElementEntity *)element; + +- (void)reloadWithElement:(MPElementEntity *)mainElement; +- (void)reloadWithTransientSite:(NSString *)siteName; @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 35e694a5..97171766 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -19,47 +19,9 @@ #import "MPPasswordCell.h" #import "MPiOSAppDelegate.h" #import "MPAppDelegate_Store.h" -#import "MPPasswordGeneratedCell.h" -#import "MPPasswordStoredCell.h" - -@interface MPPasswordCell() - -@property(strong, nonatomic) IBOutlet UILabel *nameLabel; -@property(strong, nonatomic) IBOutlet UIButton *loginButton; - -@property(nonatomic, strong) NSManagedObjectID *elementOID; -@end @implementation MPPasswordCell -+ (NSString *)reuseIdentifier { - - return NSStringFromClass( self ); -} - -+ (NSString *)reuseIdentifierForElement:(MPElementEntity *)element { - - if ([element isKindOfClass:[MPElementGeneratedEntity class]]) - return [MPPasswordGeneratedCell reuseIdentifier]; - - if ([element isKindOfClass:[MPElementStoredEntity class]]) - return [MPPasswordStoredCell reuseIdentifier]; - - return [self reuseIdentifier]; -} - -#pragma mark - UITextFieldDelegate - -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - - if (textField == self.contentField && [self.contentField.text length]) { - [self.contentField resignFirstResponder]; - return YES; - } - - return NO; -} - #pragma mark - Life cycle - (void)awakeFromNib { @@ -67,120 +29,68 @@ [super awakeFromNib]; self.layer.cornerRadius = 5; + self.layer.shadowOffset = CGSizeZero; + self.layer.shadowRadius = 5; + self.layer.shadowOpacity = 0; + self.layer.shadowColor = [UIColor whiteColor].CGColor; } -- (void)dealloc { +- (void)prepareForReuse { - [self removeKeyPathObservers]; + [super prepareForReuse]; + [self updateAnimated:NO]; } -#pragma mark - Actions +// Unblocks animations for all CALayer properties (eg. shadowOpacity) +- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event { -- (IBAction)doUser:(id)sender { + id defaultAction = [super actionForLayer:layer forKey:event]; + if (defaultAction == (id)[NSNull null] && [event isEqualToString:@"position"]) + return defaultAction; + + return NSNullToNil(defaultAction); } #pragma mark - Properties -- (void)setTransientSite:(NSString *)name { +- (void)setSelected:(BOOL)selected { - _transientSite = name; - _elementOID = nil; + [super setSelected:selected]; [self updateAnimated:YES]; } -- (void)setElement:(MPElementEntity *)element { +- (void)setHighlighted:(BOOL)highlighted { - self.elementOID = [element objectID]; -} - -- (void)setElementOID:(NSManagedObjectID *)elementOID { - - _transientSite = nil; - _elementOID = elementOID; + [super setHighlighted:highlighted]; [self updateAnimated:YES]; } -- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context { - - NSError *error = nil; - MPElementEntity *element = _elementOID? (MPElementEntity *)[context existingObjectWithID:_elementOID error:&error]: nil; - if (_elementOID && !element) - err(@"Failed to load element: %@", error); - - return element; -} - #pragma mark - Private - (void)updateAnimated:(BOOL)animated { - Weakify(self); - - if (self.transientSite) { - self.alpha = 1; - self.nameLabel.text = self.transientSite; - self.contentField.text = nil; - - [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPKey *key = [MPiOSAppDelegate get].key; - if (!key) { - self.alpha = 0; - return; - } - - MPElementType type = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType; - if (!type) - type = MPElementTypeGeneratedLong; - NSString *newContent = [MPAlgorithmDefault generateContentNamed:self.transientSite ofType:type withCounter:1 usingKey:key]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - self.contentField.text = newContent; - }]; + if (![NSThread isMainThread]) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self updateAnimated:animated]; }]; + return; } - else if (self.elementOID) { - NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; - [mainContext performBlock:^{ - [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ - Strongify(self); - NSError *error = nil; - MPKey *key = [MPiOSAppDelegate get].key; - if (!key) { - self.alpha = 0; - return; - } - MPElementEntity *element = (MPElementEntity *)[mainContext existingObjectWithID:_elementOID error:&error]; - if (!element) { - err(@"Failed to load element: %@", error); - self.alpha = 0; - return; - } - - self.alpha = 1; - [self populateWithElement:element]; - - [element resolveContentUsingKey:key result:^(NSString *result) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - Strongify(self); - self.contentField.text = result; - }]; - }]; - }]; - }]; - } - else { - [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ - self.alpha = 0; - }]; - } + [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ + self.layer.shadowOpacity = self.selected? 1: self.highlighted? 0.3f: 0; + }]; } -- (void)populateWithElement:(MPElementEntity *)element { +- (void)reloadWithElement:(MPElementEntity *)mainElement { - self.nameLabel.text = element.name; - self.contentField.text = nil; + self.nameLabel.text = mainElement.name; +} + +- (void)reloadWithTransientSite:(NSString *)siteName { + + self.nameLabel.text = strl( @"%@ - Tap to create", siteName ); } @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordElementCell.h b/MasterPassword/ObjC/iOS/MPPasswordElementCell.h new file mode 100644 index 00000000..c1781f4c --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordElementCell.h @@ -0,0 +1,31 @@ +/** + * 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 + */ + +// +// MPPasswordElementCell.h +// MPPasswordElementCell +// +// Created by lhunath on 2014-04-03. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import +#import "MPPasswordCell.h" + +@interface MPPasswordElementCell : MPPasswordCell + +@property(nonatomic, copy) NSString *transientSite; + +- (MPElementEntity *)mainElement; +- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context; +- (void)setElement:(MPElementEntity *)element; +- (void)reloadData; + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordElementCell.m b/MasterPassword/ObjC/iOS/MPPasswordElementCell.m new file mode 100644 index 00000000..0a672b8b --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordElementCell.m @@ -0,0 +1,93 @@ +/** + * 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 + */ + +// +// MPPasswordElementCell.h +// MPPasswordElementCell +// +// Created by lhunath on 2014-04-03. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPPasswordElementCell.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" + +@implementation MPPasswordElementCell { + NSManagedObjectID *_elementOID; +} + +- (void)prepareForReuse { + + _elementOID = nil; + _transientSite = nil; + + [super prepareForReuse]; +} + +- (void)setTransientSite:(NSString *)transientSite { + + if ([_transientSite isEqualToString:transientSite]) + return; + + dbg(@"transientSite: %@ -> %@", _transientSite, transientSite); + + _transientSite = transientSite; + _elementOID = nil; + + [self updateAnimated:YES]; + [self reloadData]; +} + +- (void)setElement:(MPElementEntity *)element { + + if ([_elementOID isEqual:element.objectID]) + return; + + dbg(@"element: %@ -> %@", _elementOID, element.objectID); + + _transientSite = nil; + _elementOID = element.objectID; + + [self updateAnimated:YES]; + [self reloadData]; +} + +- (MPElementEntity *)mainElement { + + return [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; +} + +- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context { + + if (!_elementOID) + return nil; + + NSError *error = nil; + MPElementEntity *element = _elementOID? (MPElementEntity *)[context existingObjectWithID:_elementOID error:&error]: nil; + if (_elementOID && !element) + err(@"Failed to load element: %@", error); + + return element; +} + +- (void)reloadData { + + if (self.transientSite) + PearlMainQueue( ^{ + [self reloadWithTransientSite:self.transientSite]; + } ); + else + [MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) { + [self reloadWithElement:[self elementInContext:mainContext]]; + }]; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.m b/MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.m deleted file mode 100644 index e6735824..00000000 --- a/MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.m +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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 - */ - -// -// MPPasswordGeneratedCell.h -// MPPasswordGeneratedCell -// -// Created by lhunath on 2014-03-19. -// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. -// - -#import "MPPasswordGeneratedCell.h" -#import "MPiOSAppDelegate.h" -#import "MPAppDelegate_Store.h" - -@interface MPPasswordGeneratedCell() - -@property(strong, nonatomic) IBOutlet UILabel *counterLabel; -@property(strong, nonatomic) IBOutlet UIButton *counterButton; -@property(strong, nonatomic) IBOutlet UIButton *upgradeButton; - -@end - -@implementation MPPasswordGeneratedCell - -- (void)populateWithElement:(MPElementEntity *)element { - - [super populateWithElement:element]; - - MPElementGeneratedEntity *generatedElement = [self generatedElement:element]; - self.counterLabel.text = strf(@"%lu", (unsigned long)generatedElement.counter); - - if (element.requiresExplicitMigration) { - self.upgradeButton.alpha = 1; - self.counterButton.alpha = 0; - } else { - self.upgradeButton.alpha = 0; - self.counterButton.alpha = 1; - } -} - -#pragma mark - Actions - -- (IBAction)doUpgrade:(UIButton *)sender { - - [MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) { - [[self elementInContext:mainContext] migrateExplicitly:YES]; - [mainContext saveToStore]; - - [self updateAnimated:YES]; - }]; -} - -- (IBAction)doIncrementCounter:(UIButton *)sender { - - [MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) { - ++[self elementInContext:mainContext].counter; - [mainContext saveToStore]; - - [self updateAnimated:YES]; - }]; -} - -#pragma mark - Properties - -- (MPElementGeneratedEntity *)elementInContext:(NSManagedObjectContext *)context { - - return [self generatedElement:[super elementInContext:context]]; -} - -- (MPElementGeneratedEntity *)generatedElement:(MPElementEntity *)element { - - NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]], @"Element is not of generated type: %@", element.name); - if (![element isKindOfClass:[MPElementGeneratedEntity class]]) - return nil; - - return (MPElementGeneratedEntity *)element; -} - -@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordLargeCell.h b/MasterPassword/ObjC/iOS/MPPasswordLargeCell.h new file mode 100644 index 00000000..b82329dc --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordLargeCell.h @@ -0,0 +1,44 @@ +/** + * 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" +#import "MPCell.h" +#import "MPPasswordCell.h" + +typedef NS_ENUM (NSUInteger, MPContentFieldMode) { + MPContentFieldModePassword, + MPContentFieldModeUser, +}; + +@interface MPPasswordLargeCell : MPPasswordCell + +@property(nonatomic) MPElementType type; +@property(nonatomic) MPContentFieldMode contentFieldMode; +@property(nonatomic, strong) IBOutlet UILabel *typeLabel; +@property(nonatomic, strong) IBOutlet UITextField *contentField; +@property(nonatomic, strong) IBOutlet UIButton *upgradeButton; + ++ (instancetype)dequeueCellWithType:(MPElementType)type fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath; + +- (void)resolveContentOfCellTypeForTransientSite:(NSString *)siteName usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock; +- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock; + +- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context; + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordLargeCell.m b/MasterPassword/ObjC/iOS/MPPasswordLargeCell.m new file mode 100644 index 00000000..82bb562d --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordLargeCell.m @@ -0,0 +1,213 @@ +/** + * 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 "MPPasswordLargeCell.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" +#import "MPPasswordLargeGeneratedCell.h" +#import "MPPasswordLargeStoredCell.h" +#import "MPPasswordTypesCell.h" + +@implementation MPPasswordLargeCell + ++ (instancetype)dequeueCellWithType:(MPElementType)type fromCollectionView:(UICollectionView *)collectionView + atIndexPath:(NSIndexPath *)indexPath { + + NSString *reuseIdentifier; + if (type & MPElementTypeClassGenerated) + reuseIdentifier = NSStringFromClass( [MPPasswordLargeGeneratedCell class] ); + else if (type & MPElementTypeClassStored) + reuseIdentifier = NSStringFromClass( [MPPasswordLargeStoredCell class] ); + else + Throw(@"Unexpected password type: %@", [MPAlgorithmDefault nameOfType:type]); + + MPPasswordLargeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; + cell.type = type; + + return cell; +} + +#pragma mark - UITextFieldDelegate + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + + [textField resignFirstResponder]; + return YES; +} + +- (void)textFieldDidEndEditing:(UITextField *)textField { + + if (textField == self.contentField) { + NSString *newContent = textField.text; + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementEntity *element = [[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context]; + + switch (self.contentFieldMode) { + case MPContentFieldModePassword: + break; + case MPContentFieldModeUser: { + element.loginName = newContent; + [context saveToStore]; + + PearlMainQueue( ^{ + [self updateAnimated:YES]; + [PearlOverlay showTemporaryOverlayWithTitle:@"Login Updated" dismissAfter:2]; + } ); + break; + } + } + }]; + } +} + +#pragma mark - Life cycle + +- (void)prepareForReuse { + + _contentFieldMode = 0; + + [super prepareForReuse]; +} + +- (void)reloadWithTransientSite:(NSString *)siteName { + + [super reloadWithTransientSite:siteName]; + + self.loginButton.alpha = 0; + self.upgradeButton.alpha = 0; + self.contentField.enabled = NO; + self.contentField.placeholder = strl( @"Tap to enter password" ); + self.typeLabel.text = [MPAlgorithmDefault nameOfType:self.type]; + + [self resolveContentOfCellTypeForTransientSite:siteName usingKey:[MPiOSAppDelegate get].key result:^(NSString *string) { + PearlMainQueue( ^{ self.contentField.text = string; } ); + }]; +} + +- (void)reloadWithElement:(MPElementEntity *)mainElement { + + [super reloadWithElement:mainElement]; + + self.loginButton.alpha = 1; + self.typeLabel.text = [mainElement.algorithm nameOfType:self.type]; + + if (mainElement.requiresExplicitMigration) + self.upgradeButton.alpha = 1; + else + self.upgradeButton.alpha = 0; + + switch (self.contentFieldMode) { + case MPContentFieldModePassword: { + if (self.type & MPElementTypeClassStored) { + self.contentField.enabled = YES; + self.contentField.placeholder = strl( @"Enter custom password" ); + } + else if (self.type & MPElementTypeClassGenerated) { + self.contentField.enabled = NO; + self.contentField.placeholder = strl( @"Generating..." ); + } + else { + self.contentField.enabled = NO; + self.contentField.placeholder = nil; + } + self.contentField.text = nil; + + MPKey *key = [MPiOSAppDelegate get].key; + + if (self.type == mainElement.type) + [mainElement resolveContentUsingKey:key result:^(NSString *string) { + PearlMainQueue( ^{ self.contentField.text = string; } ); + }]; + else + [self resolveContentOfCellTypeForElement:mainElement usingKey:key result:^(NSString *string) { + PearlMainQueue( ^{ self.contentField.text = string; } ); + }]; + break; + } + case MPContentFieldModeUser: { + self.contentField.enabled = YES; + self.contentField.placeholder = strl( @"Enter login name" ); + self.contentField.text = mainElement.loginName; + break; + } + } +} + +- (void)resolveContentOfCellTypeForTransientSite:(NSString *)siteName usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock { + + resultBlock( nil ); +} + +- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock { + + resultBlock( nil ); +} + +- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context { + + return [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:self.type]; +} + +#pragma mark - Actions + +- (IBAction)doUser:(id)sender { + + switch (self.contentFieldMode) { + case MPContentFieldModePassword: { + self.contentFieldMode = MPContentFieldModeUser; + break; + } + case MPContentFieldModeUser: { + self.contentFieldMode = MPContentFieldModePassword; + break; + } + } +} + +- (IBAction)doUpgrade:(UIButton *)sender { + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + if ([[[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context] migrateExplicitly:YES]) { + [context saveToStore]; + + PearlMainQueue( ^{ + [[MPPasswordElementCell findAsSuperviewOf:self] reloadData]; + [PearlOverlay showTemporaryOverlayWithTitle:@"Site Upgraded" dismissAfter:2]; + } ); + } + else + PearlMainQueue( ^{ + [PearlOverlay showTemporaryOverlayWithTitle:@"Site Not Upgraded" dismissAfter:2]; + } ); + }]; +} + +#pragma mark - Properties + +- (void)setContentFieldMode:(MPContentFieldMode)contentFieldMode { + + if (_contentFieldMode == contentFieldMode) + return; + + _contentFieldMode = contentFieldMode; + + [[MPPasswordElementCell findAsSuperviewOf:self] reloadData]; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.h b/MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.h similarity index 63% rename from MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.h rename to MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.h index 9e123d45..fa34073b 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.h +++ b/MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.h @@ -9,18 +9,19 @@ */ // -// MPPasswordGeneratedCell.h -// MPPasswordGeneratedCell +// MPPasswordLargeGeneratedCell.h +// MPPasswordLargeGeneratedCell // // Created by lhunath on 2014-03-19. // Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. // #import -#import "MPPasswordCell.h" +#import "MPPasswordLargeCell.h" -@interface MPPasswordGeneratedCell : MPPasswordCell +@interface MPPasswordLargeGeneratedCell : MPPasswordLargeCell -- (MPElementGeneratedEntity *)elementInContext:(NSManagedObjectContext *)context; +@property(strong, nonatomic) IBOutlet UILabel *counterLabel; +@property(strong, nonatomic) IBOutlet UIButton *counterButton; @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.m b/MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.m new file mode 100644 index 00000000..86f5036b --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.m @@ -0,0 +1,139 @@ +/** + * 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 + */ + +// +// MPPasswordLargeGeneratedCell.h +// MPPasswordLargeGeneratedCell +// +// Created by lhunath on 2014-03-19. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPPasswordLargeGeneratedCell.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" +#import "MPPasswordElementCell.h" + +@implementation MPPasswordLargeGeneratedCell + +- (void)awakeFromNib { + + [super awakeFromNib]; + + UILongPressGestureRecognizer *gestureRecognizer = [[UILongPressGestureRecognizer alloc] + initWithTarget:self action:@selector(doResetCounterRecognizer:)]; + [self.counterButton addGestureRecognizer:gestureRecognizer]; +} + +- (void)reloadWithElement:(MPElementEntity *)mainElement { + + [super reloadWithElement:mainElement]; + + MPElementGeneratedEntity *generatedElement = [self generatedElement:mainElement]; + if (generatedElement) + self.counterLabel.text = strf( @"%lu", (unsigned long)generatedElement.counter ); + else + self.counterLabel.text = @"1"; + + if (!mainElement || mainElement.requiresExplicitMigration) { + self.counterLabel.alpha = 0; + self.counterButton.alpha = 0; + } + else { + self.counterLabel.alpha = 1; + self.counterButton.alpha = 1; + } +} + +- (void)resolveContentOfCellTypeForTransientSite:(NSString *)siteName usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock { + + PearlNotMainQueue( ^{ + resultBlock( [MPAlgorithmDefault generateContentNamed:siteName ofType:self.type withCounter:1 usingKey:key] ); + } ); +} + +- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock { + + id algorithm = element.algorithm; + NSString *siteName = element.name; + + PearlNotMainQueue( ^{ + resultBlock( [algorithm generateContentNamed:siteName ofType:self.type withCounter:1 usingKey:key] ); + } ); +} + +- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context { + + element = [super saveContentTypeWithElement:element saveInContext:context]; + + MPElementGeneratedEntity *generatedElement = [self generatedElement:element]; + if (generatedElement) { + generatedElement.counter = [self.counterLabel.text intValue]; + [context saveToStore]; + } + + return element; +} + +#pragma mark - Actions + +- (IBAction)doIncrementCounter:(UIButton *)sender { + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementGeneratedEntity *generatedElement = [self generatedElementInContext:context]; + if (!generatedElement) + return; + + ++generatedElement.counter; + [context saveToStore]; + + PearlMainQueue( ^{ + [self updateAnimated:YES]; + [PearlOverlay showTemporaryOverlayWithTitle:@"Counter Incremented" dismissAfter:2]; + } ); + }]; +} + +- (void)doResetCounterRecognizer:(UILongPressGestureRecognizer *)gestureRecognizer { + + if (gestureRecognizer.state != UIGestureRecognizerStateEnded) + return; + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementGeneratedEntity *generatedElement = [self generatedElementInContext:context]; + if (!generatedElement) + return; + + generatedElement.counter = 1; + [context saveToStore]; + + PearlMainQueue( ^{ + [self updateAnimated:YES]; + [PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2]; + } ); + }]; +} + +#pragma mark - Properties + +- (MPElementGeneratedEntity *)generatedElementInContext:(NSManagedObjectContext *)context { + + return [self generatedElement:[[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context]]; +} + +- (MPElementGeneratedEntity *)generatedElement:(MPElementEntity *)element { + + if (![element isKindOfClass:[MPElementGeneratedEntity class]]) + return nil; + + return (MPElementGeneratedEntity *)element; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordStoredCell.h b/MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.h similarity index 70% rename from MasterPassword/ObjC/iOS/MPPasswordStoredCell.h rename to MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.h index 9f07f78c..3f8e4da4 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordStoredCell.h +++ b/MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.h @@ -9,18 +9,16 @@ */ // -// MPPasswordStoredCell.h -// MPPasswordStoredCell +// MPPasswordLargeStoredCell.h +// MPPasswordLargeStoredCell // // Created by lhunath on 2014-03-19. // Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. // #import -#import "MPPasswordCell.h" +#import "MPPasswordLargeCell.h" -@interface MPPasswordStoredCell : MPPasswordCell - -- (MPElementStoredEntity *)elementInContext:(NSManagedObjectContext *)context; +@interface MPPasswordLargeStoredCell : MPPasswordLargeCell @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.m b/MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.m new file mode 100644 index 00000000..12e92d83 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.m @@ -0,0 +1,112 @@ +/** + * 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 + */ + +// +// MPPasswordLargeGeneratedCell.h +// MPPasswordLargeGeneratedCell +// +// Created by lhunath on 2014-03-19. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPPasswordLargeStoredCell.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" +#import "MPPasswordElementCell.h" + +@interface MPPasswordLargeStoredCell() + +@property(strong, nonatomic) IBOutlet UIButton *editButton; + +@end + +@implementation MPPasswordLargeStoredCell + +#pragma mark - Lifecycle + +- (void)resolveContentOfCellTypeForElement:(MPElementEntity *)element usingKey:(MPKey *)key result:(void (^)(NSString *))resultBlock { + + if (element.type & MPElementTypeClassStored) + [element resolveContentUsingKey:key result:resultBlock]; + else + [super resolveContentOfCellTypeForElement:element usingKey:key result:resultBlock]; +} + +- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context { + + element = [super saveContentTypeWithElement:element saveInContext:context]; + + MPElementStoredEntity *storedElement = [self storedElement:element]; + if (storedElement) { + storedElement.contentObject = self.contentField.text; + [context saveToStore]; + } + + return element; +} + + +#pragma mark - Actions + +- (IBAction)doEditContent:(UIButton *)sender { + + UITextField *field = self.contentField; + field.enabled = YES; + [field becomeFirstResponder]; +} + +#pragma mark - UITextFieldDelegate + +- (void)textFieldDidEndEditing:(UITextField *)textField { + + [super textFieldDidEndEditing:textField]; + + if (textField == self.contentField) { + NSString *newContent = textField.text; + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPElementStoredEntity *storedElement = [self storedElementInContext:context]; + if (!storedElement) + return; + + switch (self.contentFieldMode) { + case MPContentFieldModePassword: { + storedElement.contentObject = newContent; + [context saveToStore]; + + PearlMainQueue( ^{ + [self updateAnimated:YES]; + [PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2]; + } ); + break; + } + case MPContentFieldModeUser: + break; + } + }]; + } +} + +#pragma mark - Properties + +- (MPElementStoredEntity *)storedElementInContext:(NSManagedObjectContext *)context { + + return [self storedElement:[[MPPasswordElementCell findAsSuperviewOf:self] elementInContext:context]]; +} + +- (MPElementStoredEntity *)storedElement:(MPElementEntity *)element { + + if (![element isKindOfClass:[MPElementStoredEntity class]]) + return nil; + + return (MPElementStoredEntity *)element; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordSmallCell.h b/MasterPassword/ObjC/iOS/MPPasswordSmallCell.h new file mode 100644 index 00000000..ff6bb0bb --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordSmallCell.h @@ -0,0 +1,33 @@ +/** + * 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 + */ + +// +// MPPasswordSmallCell.h +// MPPasswordSmallCell +// +// Created by lhunath on 2014-03-28. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import +#import "MPPasswordCell.h" + +@interface MPPasswordSmallCell : MPPasswordElementCell + ++ (instancetype)dequeueCellForElement:(MPElementEntity *)element + fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath; + +@end + +@interface MPPasswordSmallGeneratedCell : MPPasswordSmallCell +@end + +@interface MPPasswordSmallStoredCell : MPPasswordSmallCell +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordSmallCell.m b/MasterPassword/ObjC/iOS/MPPasswordSmallCell.m new file mode 100644 index 00000000..c89f2af9 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordSmallCell.m @@ -0,0 +1,48 @@ +/** + * 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 + */ + +// +// MPPasswordSmallCell.h +// MPPasswordSmallCell +// +// Created by lhunath on 2014-03-28. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPPasswordElementCell.h" +#import "MPPasswordSmallCell.h" + +@implementation MPPasswordSmallCell { +} + ++ (instancetype)dequeueCellForElement:(MPElementEntity *)element fromCollectionView:(UICollectionView *)collectionView + atIndexPath:(NSIndexPath *)indexPath { + + NSString *reuseIdentifier; + if (element.type & MPElementTypeClassGenerated) + reuseIdentifier = NSStringFromClass( [MPPasswordSmallGeneratedCell class] ); + else if (element.type & MPElementTypeClassStored) + reuseIdentifier = NSStringFromClass( [MPPasswordSmallStoredCell class] ); + else + Throw(@"Unexpected password type: %@", [MPAlgorithmDefault nameOfType:element.type]); + + MPPasswordSmallCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; + [cell setElement:element]; + + return cell; +} + +@end + +@implementation MPPasswordSmallGeneratedCell +@end + +@implementation MPPasswordSmallStoredCell +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordStoredCell.m b/MasterPassword/ObjC/iOS/MPPasswordStoredCell.m deleted file mode 100644 index 3c0d141f..00000000 --- a/MasterPassword/ObjC/iOS/MPPasswordStoredCell.m +++ /dev/null @@ -1,73 +0,0 @@ -/** - * 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 - */ - -// -// MPPasswordGeneratedCell.h -// MPPasswordGeneratedCell -// -// Created by lhunath on 2014-03-19. -// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. -// - -#import "MPPasswordStoredCell.h" -#import "MPiOSAppDelegate.h" -#import "MPAppDelegate_Store.h" - -@interface MPPasswordStoredCell() - -@property(strong, nonatomic) IBOutlet UIButton *editButton; - -@end - -@implementation MPPasswordStoredCell - -#pragma mark - Actions - -- (IBAction)doEditContent:(UIButton *)sender { - - self.contentField.enabled = YES; - [self.contentField becomeFirstResponder]; -} - -#pragma mark - UITextFieldDelegate - -- (void)textFieldDidEndEditing:(UITextField *)textField { - - [super textFieldDidEndEditing:textField]; - - if (textField == self.contentField) { - [MPiOSAppDelegate managedObjectContextForMainThreadPerformBlock:^(NSManagedObjectContext *mainContext) { - if (mainContext) { - [[self elementInContext:mainContext] setContentObject:self.contentField.text]; - [mainContext saveToStore]; - } - - [self updateAnimated:YES]; - }]; - } -} - -#pragma mark - Properties - -- (MPElementStoredEntity *)elementInContext:(NSManagedObjectContext *)context { - - return [self storedElement:[super elementInContext:context]]; -} - -- (MPElementStoredEntity *)storedElement:(MPElementEntity *)element { - - NSAssert([element isKindOfClass:[MPElementStoredEntity class]], @"Element is not of generated type: %@", element.name); - if (![element isKindOfClass:[MPElementStoredEntity class]]) - return nil; - - return (MPElementStoredEntity *)element; -} - -@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordTypesCell.h b/MasterPassword/ObjC/iOS/MPPasswordTypesCell.h new file mode 100644 index 00000000..f948a307 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordTypesCell.h @@ -0,0 +1,34 @@ +/** + * 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 + */ + +// +// MPPasswordTypesCell.h +// MPPasswordTypesCell +// +// Created by lhunath on 2014-03-27. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import +#import "MPCell.h" +#import "MPPasswordCell.h" +#import "MPPasswordElementCell.h" + +@interface MPPasswordTypesCell : MPPasswordElementCell + +@property(nonatomic, strong) IBOutlet UICollectionView *contentCollectionView; +@property(nonatomic, strong) id algorithm; + ++ (instancetype)dequeueCellForElement:(MPElementEntity *)element + fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath; ++ (instancetype)dequeueCellForTransientSite:(NSString *)siteName + fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath; + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordTypesCell.m b/MasterPassword/ObjC/iOS/MPPasswordTypesCell.m new file mode 100644 index 00000000..f3b739ab --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPPasswordTypesCell.m @@ -0,0 +1,218 @@ +/** + * 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 + */ + +// +// MPPasswordTypesCell.h +// MPPasswordTypesCell +// +// Created by lhunath on 2014-03-27. +// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. +// + +#import "MPPasswordTypesCell.h" +#import "MPPasswordLargeCell.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" + +@implementation MPPasswordTypesCell + +#pragma mark - Lifecycle + ++ (instancetype)dequeueCellForTransientSite:(NSString *)siteName fromCollectionView:(UICollectionView *)collectionView + atIndexPath:(NSIndexPath *)indexPath { + + MPPasswordTypesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass( [MPPasswordTypesCell class] ) + forIndexPath:indexPath]; + [cell setTransientSite:siteName]; + + return cell; +} + ++ (instancetype)dequeueCellForElement:(MPElementEntity *)element fromCollectionView:(UICollectionView *)collectionView + atIndexPath:(NSIndexPath *)indexPath { + + MPPasswordTypesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass( [MPPasswordTypesCell class] ) + forIndexPath:indexPath]; + [cell setElement:element]; + + return cell; +} + +- (void)awakeFromNib { + + [super awakeFromNib]; + + self.backgroundColor = [UIColor clearColor]; + self.layer.shadowColor = [UIColor clearColor].CGColor; + + [self prepareForReuse]; +} + +- (void)prepareForReuse { + + _algorithm = MPAlgorithmDefault; + + [super prepareForReuse]; +} + +- (void)reloadWithTransientSite:(NSString *)siteName { + + [super reloadWithTransientSite:siteName]; + + [self.contentCollectionView reloadData]; + NSIndexPath *visibleIndexPath = [self contentIndexPathForType: + IfElse([[MPiOSAppDelegate get] activeUserForMainThread].defaultType, MPElementTypeGeneratedLong)]; + [self.contentCollectionView scrollToItemAtIndexPath:visibleIndexPath atScrollPosition:NO + animated:UICollectionViewScrollPositionCenteredHorizontally]; +} + +- (void)reloadWithElement:(MPElementEntity *)mainElement { + + [super reloadWithElement:mainElement]; + + self.algorithm = IfNotNilElse([self mainElement].algorithm, MPAlgorithmDefault); + + [self.contentCollectionView reloadData]; + NSIndexPath *visibleIndexPath = [self contentIndexPathForType:mainElement.type]; + [self.contentCollectionView scrollToItemAtIndexPath:visibleIndexPath atScrollPosition:NO + animated:UICollectionViewScrollPositionCenteredHorizontally]; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + + if (!self.algorithm) + dbg_return_tr(0, @, @(section)); + + NSInteger types = 1; + + MPElementType type = [self typeForContentIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + for (MPElementType nextType = type; type != (nextType = [self.algorithm nextType:nextType]);) + ++types; + + dbg_return_tr(types, @, @(section)); +} + +- (MPPasswordLargeCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + + MPPasswordLargeCell *cell = [MPPasswordLargeCell dequeueCellWithType:[self typeForContentIndexPath:indexPath] + fromCollectionView:collectionView atIndexPath:indexPath]; + if (self.transientSite) + [cell reloadWithTransientSite:self.transientSite]; + else + [cell reloadWithElement:self.mainElement]; + + dbg_return(cell, indexPath); +} + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + + UICollectionView *passwordCollectionView = [UICollectionView findAsSuperviewOf:self]; + [passwordCollectionView.delegate collectionView:passwordCollectionView didSelectItemAtIndexPath:[passwordCollectionView indexPathForCell:self]]; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { + + if (scrollView == self.contentCollectionView) { + NSIndexPath *targetIndexPath = [self.contentCollectionView indexPathForItemAtPoint: + CGPointPlusCGPoint( *targetContentOffset, self.contentCollectionView.center )]; + *targetContentOffset = CGPointFromCGRectTopLeft( + [self.contentCollectionView layoutAttributesForItemAtIndexPath:targetIndexPath].frame ); + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + + if (scrollView == self.contentCollectionView && !decelerate) + [self saveContentType]; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + + if (scrollView == self.contentCollectionView) + [self saveContentType]; +} + +#pragma mark - Private + +- (MPElementType)typeForContentIndexPath:(NSIndexPath *)indexPath { + + MPElementType type = MPElementTypeGeneratedPIN; + + for (NSUInteger i = 0; i < indexPath.item; ++i) + type = [self.algorithm nextType:type]; + + return type; +} + +- (NSIndexPath *)contentIndexPathForType:(MPElementType)type { + + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; + while ([self typeForContentIndexPath:indexPath] != type) { + indexPath = [NSIndexPath indexPathForItem:indexPath.item + 1 inSection:indexPath.section]; + NSAssert1(indexPath.item < [self.contentCollectionView numberOfItemsInSection:0], + @"No item found for type: %@", [self.algorithm nameOfType:type]); + } + + return indexPath; +} + +- (void)saveContentType { + + if (self.transientSite) + return; + + CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds ); + NSIndexPath *centerIndexPath = [self.contentCollectionView indexPathForItemAtPoint:centerPoint]; + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPPasswordLargeCell *cell = (MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:centerIndexPath]; + if (!cell) { + err(@"Couldn't find cell to change type: centerIndexPath=%@", centerIndexPath); + return; + } + + MPElementEntity *element = [self elementInContext:context]; + if (element.type == cell.type) + // Nothing changed. + return; + + self.element = [cell saveContentTypeWithElement:element saveInContext:context]; + }]; +} + +#pragma mark - Properties + +- (void)setSelected:(BOOL)selected { + + [super setSelected:selected]; + + if (!selected) + for (NSIndexPath *indexPath in [self.contentCollectionView indexPathsForSelectedItems]) + [self.contentCollectionView deselectItemAtIndexPath:indexPath animated:YES]; +} + +- (void)setAlgorithm:(id)algorithm { + + if ([_algorithm isEqual:algorithm]) + return; + + _algorithm = algorithm; + + [self.contentCollectionView reloadData]; +} + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.h b/MasterPassword/ObjC/iOS/MPPasswordsViewController.h index d6456e1d..c439a9d2 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.h +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.h @@ -17,8 +17,9 @@ // #import "LLGitTip.h" +@class MPElementEntity; -@interface MPPasswordsViewController : UIViewController +@interface MPPasswordsViewController : UIViewController @property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer; @property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView; diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index 8f530e46..83c1b6ce 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -19,14 +19,17 @@ #import "MPPasswordsViewController.h" #import "MPiOSAppDelegate.h" #import "MPAppDelegate_Store.h" -#import "MPPasswordCell.h" +#import "MPPasswordLargeCell.h" +#import "UIScrollView+PearlAdjustInsets.h" +#import "MPPasswordTypesCell.h" +#import "MPPasswordSmallCell.h" +#import "UIColor+Expanded.h" @interface MPPasswordsViewController() -@property(strong, nonatomic) IBOutlet UINavigationBar *navigationBar; - +@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar; @property(nonatomic, readonly) NSString *query; -@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController; + @end @implementation MPPasswordsViewController { @@ -34,13 +37,23 @@ __weak id _mocObserver; NSArray *_notificationObservers; __weak UITapGestureRecognizer *_passwordsDismissRecognizer; + NSFetchedResultsController *_fetchedResultsController; + NSManagedObjectID *_activeElementOID; + BOOL _exactMatch; + NSMutableDictionary *_fetchedUpdates; + UIColor *_backgroundColor; + UIColor *_darkenedBackgroundColor; } - (void)viewDidLoad { [super viewDidLoad]; + _fetchedUpdates = [NSMutableDictionary dictionaryWithCapacity:4]; + _darkenedBackgroundColor = [(_backgroundColor = [UIColor colorWithRGBHex:0x1F2124]) colorByMultiplyingBy:0.7f]; + self.view.backgroundColor = [UIColor clearColor]; + [self.passwordCollectionView automaticallyAdjustInsetsForKeyboard]; } - (void)viewWillAppear:(BOOL)animated { @@ -59,74 +72,260 @@ [self stopObservingStore]; } -#pragma mark - UITextFieldDelegate +#pragma mark - UICollectionViewDelegateFlowLayout -- (void)textFieldDidEndEditing:(UITextField *)textField { -} +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout + sizeForItemAtIndexPath:(NSIndexPath *)indexPath { -- (BOOL)textFieldShouldReturn:(UITextField *)textField { + if (collectionView == self.passwordCollectionView) { + if (indexPath.item < 3 || + indexPath.item >= ((id)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) + return CGSizeMake( 300, 100 ); - return NO; -} + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout; + return CGSizeMake( (300 - layout.minimumInteritemSpacing) / 2, 44 ); + } -// This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification. -- (void)textFieldDidChange:(UITextField *)textField { + Throw(@"Unexpected collection view: %@", collectionView); } #pragma mark - UICollectionViewDataSource - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return [self.fetchedResultsController.sections count] + 1; + if (collectionView == self.passwordCollectionView) + dbg_return_tr([self.fetchedResultsController.sections count], @); + + Throw(@"Unexpected collection view: %@", collectionView); } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - if (collectionView == self.passwordCollectionView) { - if (section < [self.fetchedResultsController.sections count]) - return ((id)self.fetchedResultsController.sections[section]).numberOfObjects; + if (collectionView == self.passwordCollectionView) + dbg_return_tr(!self.query.length? 0: ((id)self.fetchedResultsController.sections[section]).numberOfObjects + + (_exactMatch? 0: 1), @, @(section)); - // New Site. - return [self.query length]? 1: 0; - } - - Throw(@"unexpected collection view: %@", collectionView); + Throw(@"Unexpected collection view: %@", collectionView); } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { if (collectionView == self.passwordCollectionView) { - MPPasswordCell *cell; - if (indexPath.section < [self.fetchedResultsController.sections count]) { + [UIView setAnimationsEnabled:NO]; + MPPasswordElementCell *cell; + if (indexPath.item < ((id)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) { MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; - if (indexPath.item < 2) - cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPPasswordCell reuseIdentifierForElement:element] forIndexPath:indexPath]; + if (indexPath.item < 3) + cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath]; else - cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPPasswordCell reuseIdentifierForElement:element] forIndexPath:indexPath]; - - [cell setElement:element]; - } else { - // New Site. - cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPPasswordCell reuseIdentifier] forIndexPath:indexPath]; - cell.transientSite = self.query; + cell = [MPPasswordSmallCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath]; } - return cell; + else + // New Site. + cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath]; + + [UIView setAnimationsEnabled:YES]; + dbg_return(cell, indexPath); } - Throw(@"unexpected collection view: %@", collectionView); + Throw(@"Unexpected collection view: %@", collectionView); } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + + MPPasswordElementCell *cell = (MPPasswordElementCell *)[collectionView cellForItemAtIndexPath:indexPath]; + NSString *newSiteName = cell.transientSite; + if (newSiteName) { + [PearlAlert showAlertWithTitle:@"Create Site" + message:strf( @"Do you want to create a new site named:\n%@", newSiteName ) + viewStyle:UIAlertViewStyleDefault + initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + if (buttonIndex == [alert cancelButtonIndex]) { + // Cancel + [collectionView selectItemAtIndexPath:indexPath animated:NO + scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + return; + } + + // Create + [[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) { + self.activeElement = element; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [PearlOverlay showTemporaryOverlayWithTitle:strf( @"Added %@", newSiteName ) dismissAfter:2]; + [collectionView selectItemAtIndexPath:indexPath animated:NO + scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + }]; + }]; + } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil]; + return; + } + + MPElementEntity *element = [cell mainElement]; + if (!element) { + [collectionView selectItemAtIndexPath:indexPath animated:NO + scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + return; + } + + inf(@"Copying password for: %@", element.name); + MPCheckpoint( MPCheckpointCopyToPasteboard, @{ + @"type" : NilToNSNull(element.typeName), + @"version" : @(element.version), + @"emergency" : @NO + } ); + + [element use]; + [element resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { + if (![result length]) { + [collectionView selectItemAtIndexPath:indexPath animated:NO + scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + return; + } + + [UIPasteboard generalPasteboard].string = result; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2]; + PearlMainQueueAfter( 0.1f, ^{ + [collectionView selectItemAtIndexPath:indexPath animated:NO + scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; + [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + } ); + }]; + }]; } -- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { +#pragma mark - NSFetchedResultsControllerDelegate + +- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { + + if (controller == _fetchedResultsController) { + dbg(@"controllerWillChangeContent"); + NSAssert(![_fetchedUpdates count], @"Didn't finish a previous change update?"); + if ([_fetchedUpdates count]) { + [_fetchedUpdates removeAllObjects]; + [self.passwordCollectionView reloadData]; + } + } } -#pragma mark - UILongPressGestureRecognizer +- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath + forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { -- (void)didLongPress:(UILongPressGestureRecognizer *)recognizer { + if (controller == _fetchedResultsController) { + NSMutableArray *updatesForType = _fetchedUpdates[@(type)]; + if (!updatesForType) + _fetchedUpdates[@(type)] = updatesForType = [NSMutableArray new]; + + [updatesForType addObject:@{ @"object" : NilToNSNull(anObject), + @"indexPath" : NilToNSNull(indexPath), + @"newIndexPath" : NilToNSNull(newIndexPath) }]; + switch (type) { + case NSFetchedResultsChangeInsert: + dbg(@"didChangeObject: insert: %@", [updatesForType lastObject]); + break; + case NSFetchedResultsChangeDelete: + dbg(@"didChangeObject: delete: %@", [updatesForType lastObject]); + break; + case NSFetchedResultsChangeMove: + dbg(@"didChangeObject: move: %@", [updatesForType lastObject]); + break; + case NSFetchedResultsChangeUpdate: + dbg(@"didChangeObject: update: %@", [updatesForType lastObject]); + break; + } + } } +- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id)sectionInfo + atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { + + if (controller == _fetchedResultsController) { + NSMutableArray *updatesForType = _fetchedUpdates[@(type << 3)]; + if (!updatesForType) + _fetchedUpdates[@(type << 3)] = updatesForType = [NSMutableArray new]; + + [updatesForType addObject:@{ @"sectionInfo" : NilToNSNull(sectionInfo), + @"index" : @(sectionIndex) }]; + switch (type) { + case NSFetchedResultsChangeInsert: + dbg(@"didChangeSection: insert: %@", [updatesForType lastObject]); + break; + case NSFetchedResultsChangeDelete: + dbg(@"didChangeSection: delete: %@", [updatesForType lastObject]); + break; + case NSFetchedResultsChangeMove: + dbg(@"didChangeSection: move: %@", [updatesForType lastObject]); + break; + case NSFetchedResultsChangeUpdate: + dbg(@"didChangeSection: update: %@", [updatesForType lastObject]); + break; + } + } +} + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + + if (controller == _fetchedResultsController && [_fetchedUpdates count]) { + [self.passwordCollectionView performBatchUpdates:^{ + [_fetchedUpdates enumerateKeysAndObjectsUsingBlock:^(NSNumber *typeNumber, NSArray *updates, BOOL *stop) { + BOOL updateIsSection = NO; + NSFetchedResultsChangeType type = [typeNumber unsignedIntegerValue]; + if (type >= 1 << 3) { + updateIsSection = YES; + type = type >> 3; + } + + switch (type) { + case NSFetchedResultsChangeInsert: + if (updateIsSection) { + for (NSDictionary *update in updates) { + dbg(@"insertSections:%@", update[@"index"]); + [self.passwordCollectionView insertSections: + [NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]]; + } + } + else { + dbg(@"insertItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]); + [self.passwordCollectionView insertItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.newIndexPath"]]; + } + break; + case NSFetchedResultsChangeDelete: + if (updateIsSection) { + for (NSDictionary *update in updates) { + dbg(@"deleteSections:%@", update[@"index"]); + [self.passwordCollectionView deleteSections: + [NSIndexSet indexSetWithIndex:[update[@"index"] unsignedIntegerValue]]]; + } + } + else { + dbg(@"deleteItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]); + [self.passwordCollectionView deleteItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]]; + } + break; + case NSFetchedResultsChangeMove: + NSAssert(!updateIsSection, @"Move not supported for sections"); + for (NSDictionary *update in updates) { + dbg(@"moveItemAtIndexPath:%@ toIndexPath:%@", update[@"indexPath"], update[@"newIndexPath"]); + [self.passwordCollectionView moveItemAtIndexPath:update[@"indexPath"] toIndexPath:update[@"newIndexPath"]]; + } + break; + case NSFetchedResultsChangeUpdate: + NSAssert(!updateIsSection, @"Update not supported for sections"); + dbg(@"reloadItemsAtIndexPaths:%@", [updates valueForKeyPath:@"@unionOfObjects.indexPath"]); + [self.passwordCollectionView reloadItemsAtIndexPaths:[updates valueForKeyPath:@"@unionOfObjects.indexPath"]]; + break; + } + }]; + } completion:nil]; + [_fetchedUpdates removeAllObjects]; + } +} + + #pragma mark - UIScrollViewDelegate #pragma mark - UISearchBarDelegate @@ -138,9 +337,9 @@ self.passwordsSearchBar.showsCancelButton = YES; _passwordsDismissRecognizer = [self.view dismissKeyboardForField:self.passwordsSearchBar onTouchForced:NO]; -// [UIView animateWithDuration:0.3f animations:^{ -// self.passwordCollectionView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3f]; -// }]; + [UIView animateWithDuration:0.3f animations:^{ + searchBar.superview.backgroundColor = _darkenedBackgroundColor; + }]; } } @@ -152,7 +351,7 @@ [self.view removeGestureRecognizer:_passwordsDismissRecognizer]; [UIView animateWithDuration:0.3f animations:^{ - self.passwordCollectionView.backgroundColor = [UIColor clearColor]; + searchBar.superview.backgroundColor = _backgroundColor; }]; } } @@ -195,12 +394,22 @@ self.passwordSelectionContainer.alpha = 0; }], + [[NSNotificationCenter defaultCenter] + addObserverForName:MPSignedOutNotification object:nil + queue:nil usingBlock:^(NSNotification *note) { + Strongify(self); + + self.activeElement = nil; + _fetchedResultsController = nil; + self.passwordsSearchBar.text = nil; + [self updatePasswords]; + }], [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify(self); -// [self updateMode]; TODO: reload passwords list + [self updatePasswords]; [UIView animateWithDuration:1 animations:^{ self.passwordSelectionContainer.alpha = 1; }]; @@ -217,22 +426,22 @@ - (void)observeStore { - Weakify(self); + 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]; TODO: reload passwords list + Strongify(self); + [self updatePasswords]; }]; if (!_storeObserver) _storeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify(self); - self.fetchedResultsController = nil; + _fetchedResultsController = nil; [self updatePasswords]; }]; } @@ -247,20 +456,48 @@ - (void)updatePasswords { - [self.fetchedResultsController.managedObjectContext performBlock:^{ - NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID; - if (!activeUserOID) - return; + NSString *query = self.query; + NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID; + if (!activeUserOID || ![query length]) { + self.passwordsSearchBar.text = nil; + PearlMainQueue( ^{ [self.passwordCollectionView reloadData]; } ); + return; + } + [self.fetchedResultsController.managedObjectContext performBlock:^{ NSError *error = nil; self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat: - @"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, self.query]; + @"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]; if (![self.fetchedResultsController performFetch:&error]) err(@"Couldn't fetch elements: %@", error); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - [self.passwordCollectionView reloadData]; - }]; + _exactMatch = NO; + for (MPElementEntity *entity in self.fetchedResultsController.fetchedObjects) + if ([entity.name isEqualToString:query]) { + _exactMatch = YES; + break; + } + + PearlMainQueue( ^{ + [self.passwordCollectionView performBatchUpdates:^{ + NSInteger fromSections = self.passwordCollectionView.numberOfSections; + NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView]; + for (int section = 0; section < MAX(toSections, fromSections); section++) { + if (section >= fromSections) { + dbg(@"insertSections:%d", section); + [self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]]; + } + else if (section >= toSections) { + dbg(@"deleteSections:%d", section); + [self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]]; + } + else { + dbg(@"reloadSections:%d", section); + [self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]]; + } + } + } completion:nil]; + } ); }]; } @@ -273,21 +510,30 @@ - (NSFetchedResultsController *)fetchedResultsController { - if (!_fetchedResultsController) - [MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { + if (!_fetchedResultsController) { + [MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) { NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector(lastUsed) ) ascending:NO] ]; fetchRequest.fetchBatchSize = 10; _fetchedResultsController = [[NSFetchedResultsController alloc] - initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil]; + initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil]; _fetchedResultsController.delegate = self; }]; + [self observeStore]; + } return _fetchedResultsController; } +- (void)setActiveElement:(MPElementEntity *)activeElement { + + _activeElementOID = activeElement.objectID; + + [self updatePasswords]; +} + - (void)setActive:(BOOL)active { [self setActive:active animated:YES]; diff --git a/MasterPassword/ObjC/iOS/MPUnlockViewController.m b/MasterPassword/ObjC/iOS/MPUnlockViewController.m index b6afd4d9..281319f2 100644 --- a/MasterPassword/ObjC/iOS/MPUnlockViewController.m +++ b/MasterPassword/ObjC/iOS/MPUnlockViewController.m @@ -102,11 +102,7 @@ UILabel *alertNameLabel = [self.nameLabel cloneAddedTo:container]; alertNameLabel.center = alertAvatar.center; alertNameLabel.text = user.name; - alertNameLabel.bounds = CGRectSetHeight( alertNameLabel.bounds, - [alertNameLabel.text sizeWithFont:self.nameLabel.font - constrainedToSize:CGSizeMake( alertNameLabel.bounds.size.width - 10, - 100 ) - lineBreakMode:self.nameLabel.lineBreakMode].height ); + [alertNameLabel sizeToFit]; alertNameLabel.layer.cornerRadius = 5; alertNameLabel.backgroundColor = [UIColor blackColor]; } @@ -614,10 +610,7 @@ // Lay out user name label. self.nameLabel.text = targetedAvatar? (targetedUser? targetedUser.name: @"New User"): nil; - self.nameLabel.bounds = CGRectSetHeight( self.nameLabel.bounds, - [self.nameLabel.text sizeWithFont:self.nameLabel.font - constrainedToSize:CGSizeMake( self.nameLabel.bounds.size.width - 10, 100 ) - lineBreakMode:self.nameLabel.lineBreakMode].height ); + [self.nameLabel sizeToFit]; self.oldNameLabel.bounds = self.nameLabel.bounds; if (completion) completion( YES ); @@ -725,7 +718,7 @@ - (void)setSpinnerActive:(BOOL)active { - PearlMainThread(^{ + PearlMainQueue( ^{ CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; rotate.toValue = [NSNumber numberWithDouble:2 * M_PI]; rotate.duration = 5.0; @@ -751,7 +744,7 @@ else [self avatarForUser:[self selectedUserForThread]].backgroundColor = self.avatarTemplate.backgroundColor; }]; - }); + } ); } - (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted { diff --git a/MasterPassword/ObjC/iOS/MPUsersViewController.m b/MasterPassword/ObjC/iOS/MPUsersViewController.m index 0ec581b5..bc160709 100644 --- a/MasterPassword/ObjC/iOS/MPUsersViewController.m +++ b/MasterPassword/ObjC/iOS/MPUsersViewController.m @@ -52,15 +52,18 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { __weak id _mocObserver; NSArray *_notificationObservers; NSString *_masterPasswordChoice; + NSOperationQueue *_afterUpdates; } - (void)viewDidLoad { [super viewDidLoad]; + _afterUpdates = [NSOperationQueue new]; + self.marqueeTipTexts = @[ - strl(@"Press and hold to change password or delete."), - strl(@"Shake for emergency generator."), + strl( @"Press and hold to change password or delete." ), + strl( @"Shake for emergency generator." ), ]; self.view.backgroundColor = [UIColor clearColor]; @@ -81,7 +84,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { [self reloadUsers]; [self.marqueeTipTimer invalidate]; - self.marqueeTipTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(firedMarqueeTimer:) userInfo:nil repeats:YES]; + self.marqueeTipTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(firedMarqueeTimer:) userInfo:nil + repeats:YES]; } - (void)viewWillDisappear:(BOOL)animated { @@ -119,6 +123,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { break; } case MPActiveUserStateLogin: { + [self.entryField endEditing:YES]; [self selectedAvatar].spinnerActive = YES; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { BOOL signedIn = NO, isNew = NO; @@ -178,6 +183,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { return NO; } + [self.entryField endEditing:YES]; [self selectedAvatar].spinnerActive = YES; [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { BOOL isNew = NO; @@ -240,7 +246,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { } } -#pragma mark - UICollectionViewDataSource +#pragma mark - UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { @@ -253,6 +259,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { Throw(@"unexpected collection view: %@", collectionView); } +#pragma mark - UICollectionViewDataSource + - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (collectionView == self.avatarCollectionView) @@ -272,11 +280,9 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { BOOL isNew = NO; MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady] isNew:&isNew]; - if (isNew) { - // New User + if (isNew) + // New User cell.avatar = MPAvatarAdd; - cell.name = strl( @"New User" ); - } else { // Existing User cell.avatar = user.avatar; @@ -305,6 +311,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { BOOL isNew = NO; MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady] isNew:&isNew]; + if (isNew) self.activeUserState = MPActiveUserStateUserName; else if (!user.keyID) @@ -316,9 +323,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { - if (collectionView == self.avatarCollectionView) { + if (collectionView == self.avatarCollectionView) self.activeUserState = MPActiveUserStateNone; - } } #pragma mark - UILongPressGestureRecognizer @@ -363,13 +369,12 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { 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:^{ + PearlMainQueue( ^{ NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell]; [self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; [self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath]; - }]; + } ); }]; }]; } cancelTitle:[PearlStrings get].commonButtonCancel @@ -383,9 +388,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { targetContentOffset:(inout CGPoint *)targetContentOffset { if (scrollView == self.avatarCollectionView) { - CGPoint offsetToCenter = CGPointMake( - self.avatarCollectionView.bounds.size.width / 2, - self.avatarCollectionView.bounds.size.height / 2 ); + CGPoint offsetToCenter = self.avatarCollectionView.center; NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForItemAtPoint: CGPointPlusCGPoint( *targetContentOffset, offsetToCenter )]; CGPoint targetCenter = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:avatarIndexPath].center; @@ -502,6 +505,13 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { } } +- (void)afterUpdatesMainQueue:(void (^)(void))block { + + [_afterUpdates addOperationWithBlock:^{ + PearlMainQueue( block ); + }]; +} + - (void)registerObservers { if ([_notificationObservers count]) @@ -555,7 +565,13 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify(self); - [self reloadUsers]; + NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey]; + NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey]; + if ([[NSSetUnion(insertedObjects, deletedObjects) + filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + return [evaluatedObject isKindOfClass:[MPUserEntity class]]; + }]] count]) + [self reloadUsers]; }]; if (!_storeObserver) _storeObserver = [[NSNotificationCenter defaultCenter] @@ -576,20 +592,25 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { - (void)reloadUsers { - [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; - } + [self afterUpdatesMainQueue:^{ + [self observeStore]; + [MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) { + NSError *error = nil; + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; + fetchRequest.sortDescriptors = @[ + [NSSortDescriptor sortDescriptorWithKey:NSStringFromSelector( @selector(lastUsed) ) ascending:NO] + ]; + NSArray *users = [mainContext 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; + NSMutableArray *userIDs = [NSMutableArray arrayWithCapacity:[users count]]; + for (MPUserEntity *user in users) + [userIDs addObject:user.objectID]; + self.userIDs = userIDs; + }]; }]; } @@ -616,13 +637,23 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { _userIDs = userIDs; dbg(@"userIDs -> %lu", (unsigned long)[userIDs count]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + PearlMainQueue( ^{ + BOOL isNew = NO; + NSManagedObjectID *selectUserID = [MPiOSAppDelegate get].activeUserOID; + if (!selectUserID) + selectUserID = [self selectedUserInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady] + isNew:&isNew].objectID; [self.avatarCollectionView reloadData]; + NSUInteger selectedAvatarItem = isNew? [_userIDs count]: selectUserID? [_userIDs indexOfObject:selectUserID]: NSNotFound; + if (selectedAvatarItem != NSNotFound) + [self.avatarCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:selectedAvatarItem inSection:0] animated:NO + scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; + [UIView animateWithDuration:0.3f animations:^{ self.userSelectionContainer.alpha = 1; }]; - }]; + } ); } - (void)setActiveUserState:(MPActiveUserState)activeUserState { @@ -635,11 +666,14 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { _activeUserState = activeUserState; _masterPasswordChoice = nil; - if (activeUserState != MPActiveUserStateMinimized && [MPiOSAppDelegate get].key) { + if (activeUserState != MPActiveUserStateMinimized && (!self.active || [MPiOSAppDelegate get].activeUserOID)) { [[MPiOSAppDelegate get] signOutAnimated:YES]; return; } + [_afterUpdates setSuspended:YES]; + dbg(@"suspend updates"); + __block BOOL requestFirstResponder = NO; [UIView animateWithDuration:animated? 0.3f: 0 animations:^{ MPAvatarCell *selectedAvatar = [self selectedAvatar]; @@ -647,7 +681,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { for (NSUInteger item = 0; item < [self.avatarCollectionView numberOfItemsInSection:0]; ++item) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:0]; MPAvatarCell *avatarCell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath]; - [self updateModeForAvatar:avatarCell atIndexPath:indexPath animated:NO]; + [self updateModeForAvatar:avatarCell atIndexPath:indexPath animated:animated]; if (selectedAvatar && avatarCell == selectedAvatar) [self.avatarCollectionView scrollToItemAtIndexPath:indexPath @@ -693,16 +727,6 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { break; } - // Manage the random avatar for the new user if selected. - 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. switch (activeUserState) { case MPActiveUserStateNone: { @@ -722,6 +746,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { self.avatarCollectionView.scrollEnabled = NO; self.entryContainer.alpha = 1; self.footerContainer.alpha = 1; + requestFirstResponder = YES; break; } case MPActiveUserStateMinimized: { @@ -737,12 +762,15 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { [self.avatarCollectionCenterConstraint apply]; // Toggle the keyboard. - if (!self.entryContainer.alpha) - [self.entryField resignFirstResponder]; } completion:^(BOOL finished) { - if (finished && self.entryContainer.alpha) - [self.entryField becomeFirstResponder]; + dbg(@"resume updates"); + [_afterUpdates setSuspended:NO]; }]; + + if (requestFirstResponder) + [self.entryField becomeFirstResponder]; + else + [self.entryField resignFirstResponder]; } #pragma mark - Actions diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 2a7195b0..17ecf6ed 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -13,9 +13,10 @@ @interface MPiOSAppDelegate() -@property(nonatomic, strong) PearlAlert *handleCloudContentAlert; -@property(nonatomic, strong) PearlAlert *fixCloudContentAlert; -@property(nonatomic, strong) PearlOverlay *storeLoading; +@property(nonatomic, weak) PearlAlert *handleCloudDisabledAlert; +@property(nonatomic, weak) PearlAlert *handleCloudContentAlert; +@property(nonatomic, weak) PearlAlert *fixCloudContentAlert; +@property(nonatomic, weak) PearlOverlay *storeLoading; @end @implementation MPiOSAppDelegate @@ -120,12 +121,14 @@ UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake( 0, 5, 0, 5 )]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault]; [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone]; + NSShadow *titleShadow = [NSShadow new]; + titleShadow.shadowColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f]; + titleShadow.shadowOffset = CGSizeMake( 0, -1 ); [[UINavigationBar appearance] setTitleTextAttributes: @{ - UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], - UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], - UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, -1 )], - UITextAttributeFont : [UIFont fontWithName:@"Exo-Bold" size:20.0f] + NSForegroundColorAttributeName : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], + NSShadowAttributeName : titleShadow, + NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Bold" size:20.0f] }]; UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake( 0, 5, 0, 5 )]; @@ -136,13 +139,15 @@ setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; + NSShadow *barButtonShadow = [NSShadow new]; + barButtonShadow.shadowColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f]; + barButtonShadow.shadowOffset = CGSizeMake( 0, 1 ); [[UIBarButtonItem appearance] setTitleTextAttributes: @{ - UITextAttributeTextColor : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], - UITextAttributeTextShadowColor : [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], - UITextAttributeTextShadowOffset : [NSValue valueWithUIOffset:UIOffsetMake( 0, 1 )]//, + NSForegroundColorAttributeName : [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], + NSShadowAttributeName : barButtonShadow, // Causes a bug in iOS where image views get oddly stretched... or something. - //UITextAttributeFont: [UIFont fontWithName:@"HelveticaNeue" size:13.0f] + //NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue" size:13.0f] } forState:UIControlStateNormal]; @@ -257,7 +262,7 @@ if (!importedSitesData) return; - PearlOverlay *activityOverlay = [PearlOverlay showOverlayWithTitle:@"Importing"]; + PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"]; NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) { @@ -463,9 +468,9 @@ @"--\n" @"%@" @"Master Password %@, build %@", - userName? ([userName stringByAppendingString:@"\n"]): @"", - [PearlInfoPlist get].CFBundleShortVersionString, - [PearlInfoPlist get].CFBundleVersion ) + userName? ([userName stringByAppendingString:@"\n"]): @"", + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion ) attachments:(logs ? [[PearlEMailAttachment alloc] @@ -473,8 +478,8 @@ dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain" fileName:PearlString( @"%@-%@.log", - [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]], - [PearlKeyChain deviceIdentifier] )] + [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]], + [PearlKeyChain deviceIdentifier] )] : nil), nil] showComposerForVC:viewController]; } @@ -526,17 +531,17 @@ @"--\n" @"%@\n" @"Master Password %@, build %@", - [self activeUserForMainThread].name, - [PearlInfoPlist get].CFBundleShortVersionString, - [PearlInfoPlist get].CFBundleVersion ); + [self activeUserForMainThread].name, + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion ); else message = PearlString( @"Backup of Master Password sites.\n\n\n" @"--\n" @"%@\n" @"Master Password %@, build %@", - [self activeUserForMainThread].name, - [PearlInfoPlist get].CFBundleShortVersionString, - [PearlInfoPlist get].CFBundleVersion ); + [self activeUserForMainThread].name, + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion ); NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"]; @@ -545,7 +550,7 @@ attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding] mimeType:@"text/plain" fileName: PearlString( @"%@ (%@).mpsites", [self activeUserForMainThread].name, - [exportDateFormatter stringFromDate:[NSDate date]] )], + [exportDateFormatter stringFromDate:[NSDate date]] )], nil]; } @@ -727,7 +732,7 @@ dispatch_async( dispatch_get_main_queue(), ^{ [self.handleCloudContentAlert cancelAlertAnimated:YES]; if (![self.storeLoading isVisible]) - self.storeLoading = [PearlOverlay showOverlayWithTitle:@"Loading Sites"]; + self.storeLoading = [PearlOverlay showProgressOverlayWithTitle:@"Loading Sites"]; } ); [super ubiquityStoreManager:manager willLoadStoreIsCloud:isCloudStore]; @@ -739,32 +744,47 @@ [MPiOSConfig get].iCloudEnabled = @(isCloudStore); [super ubiquityStoreManager:manager didLoadStoreForCoordinator:coordinator isCloud:isCloudStore]; - dispatch_async( dispatch_get_main_queue(), ^{ - [self.handleCloudContentAlert cancelAlertAnimated:YES]; - [self.fixCloudContentAlert cancelAlertAnimated:YES]; - [self.storeLoading cancelOverlayAnimated:YES]; - } ); + [self.handleCloudContentAlert cancelAlertAnimated:YES]; + [self.fixCloudContentAlert cancelAlertAnimated:YES]; + [self.storeLoading cancelOverlayAnimated:YES]; + [self.handleCloudDisabledAlert cancelAlertAnimated:YES]; } - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager failedLoadingStoreWithCause:(UbiquityStoreErrorCause)cause context:(id)context wasCloud:(BOOL)wasCloudStore { - dispatch_async( dispatch_get_main_queue(), ^{ - [self.storeLoading cancelOverlayAnimated:YES]; - } ); + [self.storeLoading cancelOverlayAnimated:YES]; + [self.handleCloudDisabledAlert cancelAlertAnimated:YES]; } - (BOOL)ubiquityStoreManager:(UbiquityStoreManager *)manager handleCloudContentCorruptionWithHealthyStore:(BOOL)storeHealthy { - if (manager.cloudEnabled && !storeHealthy && !([self.handleCloudContentAlert.alertView isVisible] || [self.fixCloudContentAlert.alertView isVisible])) - dispatch_async( dispatch_get_main_queue(), ^{ - [self.storeLoading cancelOverlayAnimated:YES]; - [self showCloudContentAlert]; - } ); + if (manager.cloudEnabled && !storeHealthy && !([self.handleCloudContentAlert.alertView isVisible] || [self.fixCloudContentAlert.alertView isVisible])) { + [self.storeLoading cancelOverlayAnimated:YES]; + [self.handleCloudDisabledAlert cancelAlertAnimated:YES]; + [self showCloudContentAlert]; + }; return NO; } +- (BOOL)ubiquityStoreManagerHandleCloudDisabled:(UbiquityStoreManager *)manager { + + if (![self.handleCloudDisabledAlert isVisible]) + self.handleCloudDisabledAlert = [PearlAlert showAlertWithTitle:@"iCloud Login" message: + @"You haven't added an iCloud account to your device yet.\n" + @"To add one, tap 'Wait For Me', go into Apple's Settings and add an iCloud account." + viewStyle:UIAlertViewStyleDefault initAlert:nil + tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { + if (buttonIndex == alert.firstOtherButtonIndex) + return; + + [self.storeManager reloadStore]; + } cancelTitle:@"Wait For Me" otherTitles:@"Disable iCloud", nil]; + + return YES; +} + - (void)showCloudContentAlert { __weak MPiOSAppDelegate *wSelf = self; diff --git a/MasterPassword/ObjC/iOS/MasterPassword-Info.plist b/MasterPassword/ObjC/iOS/MasterPassword-Info.plist index 20246c83..20ed0e9f 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-Info.plist +++ b/MasterPassword/ObjC/iOS/MasterPassword-Info.plist @@ -71,9 +71,10 @@ UIAppFonts - Exo-Bold.otf - Exo-ExtraBold.otf - Exo-Regular.otf + Exo2.0-Bold.otf + Exo2.0-ExtraBold.otf + Exo2.0-Regular.otf + Exo2.0-Thin.otf SourceCodePro-Black.otf SourceCodePro-ExtraLight.otf diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 879ba5e5..02a712bf 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -12,27 +12,34 @@ 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 */; }; - 93D393543ACC701C018C74DA /* PearlUIView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393676C32D23A47E27957 /* PearlUIView.m */; }; - 93D394F6D3F6E2553AA0D684 /* MPPasswordStoredCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3947F6BB69CA9A9124A5D /* MPPasswordStoredCell.m */; }; + 93D39392DEDA376F93C6C718 /* MPCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39BAA71DE51B4D8A1286C /* MPCell.m */; }; + 93D393BA1B8402D08DB40231 /* MPPasswordElementCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39342E5F115EFCC90E976 /* MPPasswordElementCell.m */; }; + 93D394F6D3F6E2553AA0D684 /* MPPasswordLargeStoredCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3947F6BB69CA9A9124A5D /* MPPasswordLargeStoredCell.m */; }; + 93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; }; 93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; }; 93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; }; - 93D3959643EACF286D0152BA /* PearlUINavigationBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DDDAC305E8ABB4220C7 /* PearlUINavigationBar.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 */; }; 93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */; }; + 93D3980046016EFD05B35BC5 /* PearlUICollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */; }; + 93D399278165FD6D950F0025 /* MPPasswordTypesCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39097C0AAE62C1C321BFC /* MPPasswordTypesCell.m */; }; 93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; }; 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; 93D399BBC0A7EC746CB1B19B /* MPLogsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D391943675426839501BB8 /* MPLogsViewController.h */; }; + 93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; }; + 93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; }; + 93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; }; 93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; }; 93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39C8AD8EAB747856B3A8C /* LLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3923B42DA2DA18F287092 /* LLModel.m */; }; - 93D39CB5E2EC1078E898F46A /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3937863061C3916AF7AD2 /* MPPasswordCell.m */; }; + 93D39CB5E2EC1078E898F46A /* MPPasswordLargeCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3937863061C3916AF7AD2 /* MPPasswordLargeCell.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 */; }; + 93D39EDD960C381D64E4DCDD /* MPPasswordSmallCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3952CC60991B97D69F26A /* MPPasswordSmallCell.m */; }; 93D39F8A9254177891F38705 /* MPSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A28369954D147E239BA /* MPSetupViewController.m */; }; - 93D39FA97F4C3F69A75D5A03 /* MPPasswordGeneratedCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3993422E207BF0B21D089 /* MPPasswordGeneratedCell.m */; }; + 93D39FA97F4C3F69A75D5A03 /* MPPasswordLargeGeneratedCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3993422E207BF0B21D089 /* MPPasswordLargeGeneratedCell.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 */; }; @@ -69,11 +76,13 @@ DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DF16406BB400B61001 /* AdSupport.framework */; settings = {ATTRIBUTES = (Required, ); }; }; DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DA672D2E14F92C6B004A189C /* libz.dylib */; }; DA672D3014F9413D004A189C /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; + DA67460D18DE7F0C00DFE240 /* Exo2.0-Thin.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460918DE7F0C00DFE240 /* Exo2.0-Thin.otf */; }; + DA67460E18DE7F0C00DFE240 /* Exo2.0-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */; }; + DA67460F18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */; }; + DA67461018DE7F0C00DFE240 /* Exo2.0-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */; }; DA69540617D975D900BF294E /* icon_gears.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37841711E29500CF925C /* icon_gears.png */; }; DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37851711E29500CF925C /* icon_gears@2x.png */; }; 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 */; }; @@ -147,9 +156,6 @@ DABD395C1711E29700CF925C /* avatar-9@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36911711E29400CF925C /* avatar-9@2x.png */; }; DABD395D1711E29700CF925C /* background.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36931711E29400CF925C /* background.png */; }; DABD395E1711E29700CF925C /* background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36941711E29400CF925C /* background@2x.png */; }; - DABD39841711E29700CF925C /* Exo-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BC1711E29500CF925C /* Exo-Bold.otf */; }; - DABD39851711E29700CF925C /* Exo-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BD1711E29500CF925C /* Exo-ExtraBold.otf */; }; - DABD39861711E29700CF925C /* Exo-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BE1711E29500CF925C /* Exo-Regular.otf */; }; DABD39871711E29700CF925C /* SourceCodePro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36BF1711E29500CF925C /* SourceCodePro-Black.otf */; }; DABD39881711E29700CF925C /* SourceCodePro-ExtraLight.otf in Resources */ = {isa = PBXBuildFile; fileRef = DABD36C01711E29500CF925C /* SourceCodePro-ExtraLight.otf */; }; DABD39A01711E29700CF925C /* icon_action.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36DA1711E29500CF925C /* icon_action.png */; }; @@ -243,8 +249,6 @@ DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; }; DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; - DACA22161705DE13002C6C22 /* UIFont+Replacement.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA22141705DE13002C6C22 /* UIFont+Replacement.m */; }; - DACA22171705DE13002C6C22 /* UIFont+Replacement.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22151705DE13002C6C22 /* UIFont+Replacement.h */; }; DACA22191705DE28002C6C22 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DACA22181705DE28002C6C22 /* Crashlytics.framework */; }; DACA22BB1705DE7D002C6C22 /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA22B71705DE7D002C6C22 /* UbiquityStoreManager.m */; }; DACA22BC1705DE7D002C6C22 /* NSError+UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */; }; @@ -373,6 +377,10 @@ DAEB938218AA537D000490CC /* x509_vfy.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB933118AA537D000490CC /* x509_vfy.h */; }; DAEB938318AA537D000490CC /* x509v3.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB933218AA537D000490CC /* x509v3.h */; }; DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; }; + DAEC85B518E3DD9A007FC0DF /* PearlUIView.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEC85B118E3DD9A007FC0DF /* PearlUIView.m */; }; + DAEC85B618E3DD9A007FC0DF /* PearlUINavigationBar.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEC85B218E3DD9A007FC0DF /* PearlUINavigationBar.m */; }; + DAEC85B718E3DD9A007FC0DF /* PearlUINavigationBar.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEC85B318E3DD9A007FC0DF /* PearlUINavigationBar.h */; }; + DAEC85B818E3DD9A007FC0DF /* PearlUIView.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEC85B418E3DD9A007FC0DF /* PearlUIView.h */; }; DAFC5656172C573B00CB5CC5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAFC5683172C57EC00CB5CC5 /* IASKAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5665172C57EC00CB5CC5 /* IASKAppSettingsViewController.m */; }; DAFC5684172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFC5667172C57EC00CB5CC5 /* IASKAppSettingsWebViewController.m */; }; @@ -502,46 +510,56 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 93D390519405B76CC6A57C4F /* MPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCell.h; sourceTree = ""; }; 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = ""; }; - 93D39083C93D90C4B94541AD /* PearlUIView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUIView.h; sourceTree = ""; }; + 93D39097C0AAE62C1C321BFC /* MPPasswordTypesCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordTypesCell.m; sourceTree = ""; }; 93D390A66F69AB1CDB0BFF93 /* LLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLModel.h; sourceTree = ""; }; - 93D390EEC85E94D9C888643F /* PearlUINavigationBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUINavigationBar.h; sourceTree = ""; }; 93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = ""; }; + 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+PearlAdjustInsets.m"; sourceTree = ""; }; + 93D391243F64A77798B4D6A4 /* MPPasswordTypesCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordTypesCell.h; sourceTree = ""; }; 93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsViewController.h; 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 = ""; }; 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = ""; }; + 93D3932D6C25F2C2D929F8A1 /* MPPasswordElementCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordElementCell.h; sourceTree = ""; }; 93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = ""; }; - 93D393676C32D23A47E27957 /* PearlUIView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUIView.m; sourceTree = ""; }; - 93D3937863061C3916AF7AD2 /* MPPasswordCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordCell.m; sourceTree = ""; }; + 93D39342E5F115EFCC90E976 /* MPPasswordElementCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordElementCell.m; sourceTree = ""; }; + 93D3937863061C3916AF7AD2 /* MPPasswordLargeCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordLargeCell.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; }; 93D3942A356B639724157982 /* PearlOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlOverlay.h; sourceTree = ""; }; - 93D3947F6BB69CA9A9124A5D /* MPPasswordStoredCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordStoredCell.m; sourceTree = ""; }; + 93D3947F6BB69CA9A9124A5D /* MPPasswordLargeStoredCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordLargeStoredCell.m; sourceTree = ""; }; + 93D3952CC60991B97D69F26A /* MPPasswordSmallCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordSmallCell.m; sourceTree = ""; }; 93D3956915634581E737B38C /* PearlNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlNavigationController.m; sourceTree = ""; }; - 93D395BA6B2CFF5F49A4D25F /* MPPasswordStoredCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordStoredCell.h; sourceTree = ""; }; + 93D395BA6B2CFF5F49A4D25F /* MPPasswordLargeStoredCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordLargeStoredCell.h; sourceTree = ""; }; 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = ""; }; 93D3971FE104BB4052484151 /* MPUsersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUsersViewController.h; sourceTree = ""; }; 93D39730673227EFF6DEFF19 /* MPSetupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSetupViewController.h; sourceTree = ""; }; 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogsViewController.m; sourceTree = ""; }; 93D3983278751A530262F64E /* LLConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLConfig.h; sourceTree = ""; }; 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = ""; }; + 93D39888EE06F06264CC963B /* MPPasswordSmallCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordSmallCell.h; sourceTree = ""; }; 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = ""; }; - 93D3993422E207BF0B21D089 /* MPPasswordGeneratedCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordGeneratedCell.m; sourceTree = ""; }; + 93D3993422E207BF0B21D089 /* MPPasswordLargeGeneratedCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordLargeGeneratedCell.m; sourceTree = ""; }; + 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = ""; }; 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = ""; }; 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 = ""; }; + 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUICollectionView.h; 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 = ""; }; + 93D39BAA71DE51B4D8A1286C /* MPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCell.m; 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 = ""; }; - 93D39CE1138FDA4D3D1B847A /* MPPasswordGeneratedCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordGeneratedCell.h; sourceTree = ""; }; + 93D39CE1138FDA4D3D1B847A /* MPPasswordLargeGeneratedCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordLargeGeneratedCell.h; sourceTree = ""; }; 93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCombinedViewController.h; sourceTree = ""; }; + 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUICollectionView.m; sourceTree = ""; }; 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAvatarCell.h; sourceTree = ""; }; - 93D39DDDAC305E8ABB4220C7 /* PearlUINavigationBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUINavigationBar.m; sourceTree = ""; }; - 93D39E02F69CACAB61C056F8 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = ""; }; + 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+PearlAdjustInsets.h"; sourceTree = ""; }; + 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordCell.m; sourceTree = ""; }; + 93D39E02F69CACAB61C056F8 /* MPPasswordLargeCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordLargeCell.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; }; @@ -579,8 +597,11 @@ DA6701DD16406B7300B61001 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; DA6701DF16406BB400B61001 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; + DA67460918DE7F0C00DFE240 /* Exo2.0-Thin.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Thin.otf"; sourceTree = ""; }; + DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Regular.otf"; sourceTree = ""; }; + DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-ExtraBold.otf"; sourceTree = ""; }; + DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Bold.otf"; sourceTree = ""; }; 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 = ""; }; @@ -722,9 +743,6 @@ DABD36911711E29400CF925C /* avatar-9@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-9@2x.png"; sourceTree = ""; }; DABD36931711E29400CF925C /* background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = background.png; sourceTree = ""; }; DABD36941711E29400CF925C /* background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "background@2x.png"; sourceTree = ""; }; - DABD36BC1711E29500CF925C /* Exo-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-Bold.otf"; sourceTree = ""; }; - DABD36BD1711E29500CF925C /* Exo-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-ExtraBold.otf"; sourceTree = ""; }; - DABD36BE1711E29500CF925C /* Exo-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-Regular.otf"; sourceTree = ""; }; DABD36BF1711E29500CF925C /* SourceCodePro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-Black.otf"; sourceTree = ""; }; DABD36C01711E29500CF925C /* SourceCodePro-ExtraLight.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-ExtraLight.otf"; sourceTree = ""; }; DABD36DA1711E29500CF925C /* icon_action.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_action.png; sourceTree = ""; }; @@ -1305,8 +1323,6 @@ DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; }; DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Pearl-Prefix.pch"; path = "../../MasterPassword/ObjC/Pearl/Pearl-Prefix.pch"; sourceTree = ""; }; - DACA22141705DE13002C6C22 /* UIFont+Replacement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Replacement.m"; sourceTree = ""; }; - DACA22151705DE13002C6C22 /* UIFont+Replacement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+Replacement.h"; sourceTree = ""; }; DACA22181705DE28002C6C22 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = ""; }; DACA22B71705DE7D002C6C22 /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = ""; }; DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+UbiquityStoreManager.h"; sourceTree = ""; }; @@ -1439,6 +1455,10 @@ DAEB933118AA537D000490CC /* x509_vfy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x509_vfy.h; sourceTree = ""; }; DAEB933218AA537D000490CC /* x509v3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x509v3.h; sourceTree = ""; }; DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + DAEC85B118E3DD9A007FC0DF /* PearlUIView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUIView.m; sourceTree = ""; }; + DAEC85B218E3DD9A007FC0DF /* PearlUINavigationBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUINavigationBar.m; sourceTree = ""; }; + DAEC85B318E3DD9A007FC0DF /* PearlUINavigationBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUINavigationBar.h; sourceTree = ""; }; + DAEC85B418E3DD9A007FC0DF /* PearlUIView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUIView.h; sourceTree = ""; }; DAFC5655172C573B00CB5CC5 /* libInAppSettingsKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libInAppSettingsKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; DAFC5659172C573B00CB5CC5 /* InAppSettingsKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "InAppSettingsKit-Prefix.pch"; sourceTree = ""; }; DAFC565A172C573B00CB5CC5 /* InAppSettingsKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InAppSettingsKit.h; sourceTree = ""; }; @@ -1585,7 +1605,6 @@ DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */, DA6701DE16406B7300B61001 /* Social.framework in Frameworks */, DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */, - DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */, DA44260A1557D9E40052177D /* libUbiquityStoreManager.a in Frameworks */, DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */, DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */, @@ -1608,14 +1627,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DA829E4E159847E0002417D3 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DA829E52159847E0002417D3 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DAC6325A1486805C0075AEA5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1688,14 +1699,8 @@ DAFC5657172C573B00CB5CC5 /* InAppSettingsKit */, DA5BFA47147E415C00F98B1E /* Frameworks */, DA5BFA45147E415C00F98B1E /* Products */, - 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */, - 93D3971FE104BB4052484151 /* MPUsersViewController.h */, - 93D393676C32D23A47E27957 /* PearlUIView.m */, - 93D39083C93D90C4B94541AD /* PearlUIView.h */, - 93D39E02F69CACAB61C056F8 /* MPPasswordCell.h */, - 93D3937863061C3916AF7AD2 /* MPPasswordCell.m */, - 93D395BA6B2CFF5F49A4D25F /* MPPasswordStoredCell.h */, - 93D3947F6BB69CA9A9124A5D /* MPPasswordStoredCell.m */, + 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */, + 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */, ); sourceTree = ""; }; @@ -1708,7 +1713,6 @@ DAC6326C148680650075AEA5 /* libjrswizzle.a */, DAD3127115528CD200A3F9ED /* libLocalytics.a */, DA4425CB1557BED40052177D /* libUbiquityStoreManager.a */, - DA829E51159847E0002417D3 /* libFontReplacer.a */, DAFC5655172C573B00CB5CC5 /* libInAppSettingsKit.a */, DAE1EF2917ED112600BC0086 /* libDCIntrospect.a */, DADEF4221810D5530052CA3E /* libLoveLyndir.a */, @@ -1965,9 +1969,10 @@ DABD36BB1711E29500CF925C /* Fonts */ = { isa = PBXGroup; children = ( - DABD36BC1711E29500CF925C /* Exo-Bold.otf */, - DABD36BD1711E29500CF925C /* Exo-ExtraBold.otf */, - DABD36BE1711E29500CF925C /* Exo-Regular.otf */, + DA67460918DE7F0C00DFE240 /* Exo2.0-Thin.otf */, + DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */, + DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */, + DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */, DABD36BF1711E29500CF925C /* SourceCodePro-Black.otf */, DABD36C01711E29500CF925C /* SourceCodePro-ExtraLight.otf */, ); @@ -2552,10 +2557,22 @@ DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */, 93D39B381350802A194BF332 /* MPAvatarCell.m */, 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */, - 93D39DDDAC305E8ABB4220C7 /* PearlUINavigationBar.m */, - 93D390EEC85E94D9C888643F /* PearlUINavigationBar.h */, - 93D3993422E207BF0B21D089 /* MPPasswordGeneratedCell.m */, - 93D39CE1138FDA4D3D1B847A /* MPPasswordGeneratedCell.h */, + 93D3993422E207BF0B21D089 /* MPPasswordLargeGeneratedCell.m */, + 93D39CE1138FDA4D3D1B847A /* MPPasswordLargeGeneratedCell.h */, + 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */, + 93D3971FE104BB4052484151 /* MPUsersViewController.h */, + 93D39E02F69CACAB61C056F8 /* MPPasswordLargeCell.h */, + 93D3937863061C3916AF7AD2 /* MPPasswordLargeCell.m */, + 93D395BA6B2CFF5F49A4D25F /* MPPasswordLargeStoredCell.h */, + 93D3947F6BB69CA9A9124A5D /* MPPasswordLargeStoredCell.m */, + 93D39BAA71DE51B4D8A1286C /* MPCell.m */, + 93D390519405B76CC6A57C4F /* MPCell.h */, + 93D39097C0AAE62C1C321BFC /* MPPasswordTypesCell.m */, + 93D391243F64A77798B4D6A4 /* MPPasswordTypesCell.h */, + 93D3952CC60991B97D69F26A /* MPPasswordSmallCell.m */, + 93D39888EE06F06264CC963B /* MPPasswordSmallCell.h */, + 93D39342E5F115EFCC90E976 /* MPPasswordElementCell.m */, + 93D3932D6C25F2C2D929F8A1 /* MPPasswordElementCell.h */, ); path = iOS; sourceTree = ""; @@ -2581,7 +2598,6 @@ DABF632217B744F900DA7E38 /* GoogleOpenSource.framework */, DABF632317B744F900DA7E38 /* GooglePlus.framework */, DACA22181705DE28002C6C22 /* Crashlytics.framework */, - DACA22131705DE13002C6C22 /* UIFont+Replacement */, DAFC5662172C57EC00CB5CC5 /* InAppSettingsKit */, DACA22C71705DEB0002C6C22 /* Localytics */, DAC77CAF148291A600BCF976 /* Pearl */, @@ -2592,16 +2608,6 @@ path = ../../../External; sourceTree = ""; }; - DACA22131705DE13002C6C22 /* UIFont+Replacement */ = { - isa = PBXGroup; - children = ( - DACA22141705DE13002C6C22 /* UIFont+Replacement.m */, - DACA22151705DE13002C6C22 /* UIFont+Replacement.h */, - ); - name = "UIFont+Replacement"; - path = "FontReplacer/UIFont+Replacement"; - sourceTree = ""; - }; DACA22B61705DE7D002C6C22 /* UbiquityStoreManager */ = { isa = PBXGroup; children = ( @@ -3045,6 +3051,10 @@ DAFE460715039823003ABA7C /* Pearl-UIKit */ = { isa = PBXGroup; children = ( + DAEC85B118E3DD9A007FC0DF /* PearlUIView.m */, + DAEC85B218E3DD9A007FC0DF /* PearlUINavigationBar.m */, + DAEC85B318E3DD9A007FC0DF /* PearlUINavigationBar.h */, + DAEC85B418E3DD9A007FC0DF /* PearlUIView.h */, DA2CA4E518D2AC10007798F8 /* NSLayoutConstraint+PearlUIKit.m */, DA2CA4E218D28866007798F8 /* NSLayoutConstraint+PearlUIKit.h */, 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */, @@ -3094,6 +3104,10 @@ 93D3942A356B639724157982 /* PearlOverlay.h */, 93D3956915634581E737B38C /* PearlNavigationController.m */, 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */, + 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */, + 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */, + 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */, + 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */, ); path = "Pearl-UIKit"; sourceTree = ""; @@ -3118,14 +3132,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DA829E4F159847E0002417D3 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - DACA22171705DE13002C6C22 /* UIFont+Replacement.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DAC6325B1486805C0075AEA5 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -3207,6 +3213,7 @@ DAEB933C18AA537D000490CC /* aes.h in Headers */, DAEB937618AA537D000490CC /* ssl2.h in Headers */, DAFE4A3C15039824003ABA7C /* Pearl-UIKit-Dependencies.h in Headers */, + DAEC85B818E3DD9A007FC0DF /* PearlUIView.h in Headers */, DAEB935B18AA537D000490CC /* krb5_asn.h in Headers */, DAEB935818AA537D000490CC /* evp.h in Headers */, DAEB934118AA537D000490CC /* blowfish.h in Headers */, @@ -3275,6 +3282,7 @@ DAEB935C18AA537D000490CC /* kssl.h in Headers */, DAEB933418AA537D000490CC /* crypto_scrypt.h in Headers */, DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */, + DAEC85B718E3DD9A007FC0DF /* PearlUINavigationBar.h in Headers */, DAEB934918AA537D000490CC /* conf_api.h in Headers */, 93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */, DAEB937518AA537D000490CC /* ssl.h in Headers */, @@ -3282,6 +3290,8 @@ 93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */, DAEB936218AA537D000490CC /* obj_mac.h in Headers */, DAEB934218AA537D000490CC /* bn.h in Headers */, + 93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */, + 93D3980046016EFD05B35BC5 /* PearlUICollectionView.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3324,6 +3334,7 @@ DA5BFA40147E415C00F98B1E /* Sources */, DA5BFA41147E415C00F98B1E /* Frameworks */, DA5BFA42147E415C00F98B1E /* Resources */, + DA67460818DE7B2C00DFE240 /* Run Script: Moarfonts */, DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */, DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */, ); @@ -3336,23 +3347,6 @@ productReference = DA5BFA44147E415C00F98B1E /* MasterPassword.app */; productType = "com.apple.product-type.application"; }; - DA829E50159847E0002417D3 /* FontReplacer */ = { - isa = PBXNativeTarget; - buildConfigurationList = DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */; - buildPhases = ( - DA829E4D159847E0002417D3 /* Sources */, - DA829E4E159847E0002417D3 /* Frameworks */, - DA829E4F159847E0002417D3 /* Headers */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = FontReplacer; - productName = FontReplacer; - productReference = DA829E51159847E0002417D3 /* libFontReplacer.a */; - productType = "com.apple.product-type.library.static"; - }; DAC6325C1486805C0075AEA5 /* uicolor-utilities */ = { isa = PBXNativeTarget; buildConfigurationList = DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */; @@ -3597,7 +3591,6 @@ DAC6326B148680650075AEA5 /* jrswizzle */, DAD3127015528CD200A3F9ED /* Localytics */, DA4425CA1557BED40052177D /* UbiquityStoreManager */, - DA829E50159847E0002417D3 /* FontReplacer */, DAFC5654172C573B00CB5CC5 /* InAppSettingsKit */, DAE1EF2817ED112600BC0086 /* DCIntrospect */, DADEF4211810D5530052CA3E /* LoveLyndir */, @@ -3642,6 +3635,7 @@ DABD391D1711E29700CF925C /* ui_spinner.png in Resources */, DABD391E1711E29700CF925C /* ui_spinner@2x.png in Resources */, DA69540617D975D900BF294E /* icon_gears.png in Resources */, + DA67460D18DE7F0C00DFE240 /* Exo2.0-Thin.otf in Resources */, DABD39271711E29700CF925C /* ui_textfield.png in Resources */, DABD39281711E29700CF925C /* ui_textfield@2x.png in Resources */, DABD39291711E29700CF925C /* ui_toolbar_container.png in Resources */, @@ -3656,6 +3650,7 @@ DABD393D1711E29700CF925C /* avatar-11@2x.png in Resources */, DABD393E1711E29700CF925C /* avatar-12.png in Resources */, DABD393F1711E29700CF925C /* avatar-12@2x.png in Resources */, + DA67461018DE7F0C00DFE240 /* Exo2.0-Bold.otf in Resources */, DABD39401711E29700CF925C /* avatar-13.png in Resources */, DABD39411711E29700CF925C /* avatar-13@2x.png in Resources */, DABD39421711E29700CF925C /* avatar-14.png in Resources */, @@ -3670,9 +3665,11 @@ DABD394A1711E29700CF925C /* avatar-18.png in Resources */, DABD394B1711E29700CF925C /* avatar-18@2x.png in Resources */, DABD394C1711E29700CF925C /* avatar-1@2x.png in Resources */, + DA67460E18DE7F0C00DFE240 /* Exo2.0-Regular.otf in Resources */, DABD394D1711E29700CF925C /* avatar-2.png in Resources */, DABD394E1711E29700CF925C /* avatar-2@2x.png in Resources */, DABD394F1711E29700CF925C /* avatar-3.png in Resources */, + DA67460F18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf in Resources */, DABD39501711E29700CF925C /* avatar-3@2x.png in Resources */, DABD39511711E29700CF925C /* avatar-4.png in Resources */, DABD39521711E29700CF925C /* avatar-4@2x.png in Resources */, @@ -3689,9 +3686,6 @@ DABD395C1711E29700CF925C /* avatar-9@2x.png in Resources */, DABD395D1711E29700CF925C /* background.png in Resources */, DABD395E1711E29700CF925C /* background@2x.png in Resources */, - DABD39841711E29700CF925C /* Exo-Bold.otf in Resources */, - DABD39851711E29700CF925C /* Exo-ExtraBold.otf in Resources */, - DABD39861711E29700CF925C /* Exo-Regular.otf in Resources */, DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */, DABD39871711E29700CF925C /* SourceCodePro-Black.otf in Resources */, DADEF4161810D2940052CA3E /* love-lyndir.button.red.png in Resources */, @@ -3794,6 +3788,21 @@ shellPath = "/bin/bash -e"; shellScript = "PATH+=:/usr/libexec\n\naddPlistWithKey() {\n local key=$1 type=$2 value=$3 plist=${4:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Delete :'$key'\" \"$plist\" 2>/dev/null || true\n PlistBuddy -c \"Add :'$key' '$type' '$value'\" \"$plist\"\n}\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' '$value'\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\ndescription=$(git describe --always --dirty --long)\nversion=${description%-g*}\nIFS=- read major minor <<< \"$version\"\nprintf -v version '%s.%02d' \"$major\" \"$minor\"\nprintf -v commit '%09d' \"$((16#${description##*-g}))\"\n\naddPlistWithKey GITDescription string \"$description\"\nsetPlistWithKey CFBundleVersion \"${version//.}$commit\" # No separator between version and commit because I had already submitted a CFBundleVersion with a really high major. Cry.\nsetPlistWithKey CFBundleShortVersionString \"$version\"\n\nsetSettingWithTitle \"Build\" \"$commit\"\nsetSettingWithTitle \"Version\" \"$version\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n\nif [[ $DEPLOYMENT_LOCATION = YES ]]; then\n # This build is a release. Do some release checks.\n passed=1\n [[ $description != *-dirty ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release a dirty version, first commit any changes.\"; }\n [[ $(PlistBuddy -c \"Print :'API Key'\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Crashlytics.plist\") ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release: Crashlytics API key is missing.\"; }\n [[ $(PlistBuddy -c \"Print :'ClientID'\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Google+.plist\") ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release: Google+ ClientID is missing.\"; }\n [[ $(PlistBuddy -c \"Print :'Key.distribution'\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Localytics.plist\") ]] || \\\n { passed=0; echo >&2 \"ERROR: Cannot release: Localytics distribution key is missing.\"; }\n (( passed )) || \\\n { echo >&2 \"Failed to pass release checks. Fix the above errors and re-try. Aborting.\"; exit 1; }\nfi"; }; + DA67460818DE7B2C00DFE240 /* Run Script: Moarfonts */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script: Moarfonts"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = "/bin/bash -e"; + shellScript = "[[ -x /usr/local/bin/moarfonts ]] || {\n echo >&2 \"moarfonts not installed, embedded fonts will not show up in IB.\"\n exit\n}\n\nfind \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}\" -name '*.otf' -exec /usr/local/bin/moarfonts install {} +"; + showEnvVarsInLog = 0; + }; DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3858,19 +3867,14 @@ 93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */, 93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */, 93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */, - 93D3959643EACF286D0152BA /* PearlUINavigationBar.m in Sources */, - 93D393543ACC701C018C74DA /* PearlUIView.m in Sources */, - 93D39CB5E2EC1078E898F46A /* MPPasswordCell.m in Sources */, - 93D39FA97F4C3F69A75D5A03 /* MPPasswordGeneratedCell.m in Sources */, - 93D394F6D3F6E2553AA0D684 /* MPPasswordStoredCell.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DA829E4D159847E0002417D3 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DACA22161705DE13002C6C22 /* UIFont+Replacement.m in Sources */, + 93D39CB5E2EC1078E898F46A /* MPPasswordLargeCell.m in Sources */, + 93D39FA97F4C3F69A75D5A03 /* MPPasswordLargeGeneratedCell.m in Sources */, + 93D394F6D3F6E2553AA0D684 /* MPPasswordLargeStoredCell.m in Sources */, + 93D39392DEDA376F93C6C718 /* MPCell.m in Sources */, + 93D399278165FD6D950F0025 /* MPPasswordTypesCell.m in Sources */, + 93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */, + 93D39EDD960C381D64E4DCDD /* MPPasswordSmallCell.m in Sources */, + 93D393BA1B8402D08DB40231 /* MPPasswordElementCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3924,6 +3928,7 @@ DAFE4A5115039824003ABA7C /* PearlUIDebug.m in Sources */, DAFE4A5315039824003ABA7C /* PearlUIUtils.m in Sources */, DAFE4A5515039824003ABA7C /* PearlValidatingTextField.m in Sources */, + DAEC85B618E3DD9A007FC0DF /* PearlUINavigationBar.m in Sources */, DAFE4A5715039824003ABA7C /* PearlWebViewController.m in Sources */, DAFE4A5915039824003ABA7C /* UIImage+PearlScaling.m in Sources */, DAFE4A62150399FF003ABA7C /* PearlAppDelegate.m in Sources */, @@ -3934,6 +3939,7 @@ DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */, DAFE4A63150399FF003ABA82 /* UIControl+PearlBlocks.m in Sources */, DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */, + DAEC85B518E3DD9A007FC0DF /* PearlUIView.m in Sources */, DA2CA4DD18D28859007798F8 /* NSArray+Pearl.m in Sources */, DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */, DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */, @@ -3947,6 +3953,8 @@ 93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */, 93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */, DA2CA4DF18D28859007798F8 /* NSTimer+PearlBlock.m in Sources */, + 93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */, + 93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4244,6 +4252,7 @@ EXCLUDED_SOURCE_FILE_NAMES = libTestFlight.a; GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; OTHER_LDFLAGS = ( "$(inherited)", "-framework", @@ -4269,6 +4278,7 @@ EXCLUDED_SOURCE_FILE_NAMES = ""; GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "4CBD21E7-DB60-4F7F-80F8-98DFA83D2CE0"; SKIP_INSTALL = NO; @@ -4277,27 +4287,6 @@ }; name = "AdHoc-iOS"; }; - DA829E5A159847E0002417D3 /* Debug-iOS */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_WARN_INHIBIT_ALL_WARNINGS = YES; - }; - name = "Debug-iOS"; - }; - DA829E5B159847E0002417D3 /* AdHoc-iOS */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_WARN_INHIBIT_ALL_WARNINGS = YES; - }; - name = "AdHoc-iOS"; - }; - DA829E5C159847E0002417D3 /* AppStore-iOS */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_WARN_INHIBIT_ALL_WARNINGS = YES; - }; - name = "AppStore-iOS"; - }; DA95D60914DF3F3B008D1B94 /* AppStore-iOS */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4405,6 +4394,7 @@ ); GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "6C6B84DD-9D8F-4321-BD77-5F737DBE1778"; SKIP_INSTALL = NO; @@ -4677,16 +4667,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "AdHoc-iOS"; }; - DA829E59159847E0002417D3 /* Build configuration list for PBXNativeTarget "FontReplacer" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DA829E5A159847E0002417D3 /* Debug-iOS */, - DA829E5B159847E0002417D3 /* AdHoc-iOS */, - DA829E5C159847E0002417D3 /* AppStore-iOS */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = "AdHoc-iOS"; - }; DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MasterPassword/ObjC/iOS/PearlUINavigationBar.m b/MasterPassword/ObjC/iOS/PearlUINavigationBar.m deleted file mode 100644 index 7edcab3c..00000000 --- a/MasterPassword/ObjC/iOS/PearlUINavigationBar.m +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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 - */ - -// -// PearlUINavigationBar.h -// PearlUINavigationBar -// -// Created by lhunath on 2014-03-17. -// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. -// - -#import "PearlUINavigationBar.h" - -@implementation PearlUINavigationBar - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - - UIView *hitView = [super hitTest:point withEvent:event]; - if (self.ignoreTouches && hitView == self) - return nil; - - return hitView; -} - -- (void)setInvisible:(BOOL)invisible { - - _invisible = invisible; - - if (invisible) { - self.translucent = YES; - self.shadowImage = [UIImage new]; - [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault]; - } -} - -@end diff --git a/MasterPassword/ObjC/iOS/PearlUIView.m b/MasterPassword/ObjC/iOS/PearlUIView.m deleted file mode 100644 index 7e759804..00000000 --- a/MasterPassword/ObjC/iOS/PearlUIView.m +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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 - */ - -// -// PearlUIView.h -// PearlUIView -// -// Created by lhunath on 2014-03-17. -// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. -// - -#import "PearlUIView.h" - -@implementation PearlUIView - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - - UIView *hitView = [super hitTest:point withEvent:event]; - if (self.ignoreTouches && hitView == self) - return nil; - - return hitView; -} - -@end diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index 4485c0b5..72696868 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -1,7 +1,7 @@ - + @@ -50,11 +50,11 @@ - + - + @@ -63,13 +63,13 @@ - + @@ -90,7 +90,7 @@ - + @@ -123,7 +123,7 @@ - + @@ -164,8 +164,11 @@ + + + - + @@ -224,6 +227,9 @@ + + + @@ -241,20 +247,13 @@ - - - - - - - @@ -404,416 +403,328 @@ - + - + - + - - + + - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - + + + - + + + + - - + + - + - - - - - - - - - - - - + - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + @@ -1124,7 +853,6 @@ - diff --git a/MasterPassword/Resources/Media/Fonts/Exo-Bold.otf b/MasterPassword/Resources/Media/Fonts/Exo-Bold.otf deleted file mode 100644 index 02ff3b55..00000000 Binary files a/MasterPassword/Resources/Media/Fonts/Exo-Bold.otf and /dev/null differ diff --git a/MasterPassword/Resources/Media/Fonts/Exo-ExtraBold.otf b/MasterPassword/Resources/Media/Fonts/Exo-ExtraBold.otf deleted file mode 100644 index 1e1c3311..00000000 Binary files a/MasterPassword/Resources/Media/Fonts/Exo-ExtraBold.otf and /dev/null differ diff --git a/MasterPassword/Resources/Media/Fonts/Exo-Regular.otf b/MasterPassword/Resources/Media/Fonts/Exo-Regular.otf deleted file mode 100644 index 4c4691f2..00000000 Binary files a/MasterPassword/Resources/Media/Fonts/Exo-Regular.otf and /dev/null differ diff --git a/MasterPassword/Resources/Media/Fonts/Exo2.0-Bold.otf b/MasterPassword/Resources/Media/Fonts/Exo2.0-Bold.otf new file mode 100644 index 00000000..1e7072dd Binary files /dev/null and b/MasterPassword/Resources/Media/Fonts/Exo2.0-Bold.otf differ diff --git a/MasterPassword/Resources/Media/Fonts/Exo2.0-ExtraBold.otf b/MasterPassword/Resources/Media/Fonts/Exo2.0-ExtraBold.otf new file mode 100644 index 00000000..32dadb3f Binary files /dev/null and b/MasterPassword/Resources/Media/Fonts/Exo2.0-ExtraBold.otf differ diff --git a/MasterPassword/Resources/Media/Fonts/Exo2.0-Regular.otf b/MasterPassword/Resources/Media/Fonts/Exo2.0-Regular.otf new file mode 100644 index 00000000..4bd82f98 Binary files /dev/null and b/MasterPassword/Resources/Media/Fonts/Exo2.0-Regular.otf differ diff --git a/MasterPassword/Resources/Media/Fonts/Exo2.0-Thin.otf b/MasterPassword/Resources/Media/Fonts/Exo2.0-Thin.otf new file mode 100644 index 00000000..768f03f3 Binary files /dev/null and b/MasterPassword/Resources/Media/Fonts/Exo2.0-Thin.otf differ