From bd37f1d6a7a61edeb9fad3b354f9ac8fb1e8f61a Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sun, 6 Apr 2014 23:34:18 -0400 Subject: [PATCH] Completed password cell handling and misc UI and moc update improvements. [UPDATED] Make private moc parent of all private blocks to avoid blocking the main thread for writes, update the main moc on private moc updates. [FIXED] Don't cause a crash on elements with a bad type. [UPDATED] Improved cell handling and UI update handling. [UPDATED] Replace FontReplacer with moarfonts to fix issues in UICollectionViewCells. --- External/FontReplacer | 1 - External/Pearl | 2 +- MasterPassword/ObjC/MPAlgorithmV0.m | 35 +- MasterPassword/ObjC/MPAppDelegate_Key.m | 2 +- MasterPassword/ObjC/MPAppDelegate_Shared.m | 5 +- MasterPassword/ObjC/MPAppDelegate_Store.h | 2 +- MasterPassword/ObjC/MPAppDelegate_Store.m | 52 +- MasterPassword/ObjC/MPEntities.m | 14 +- .../ObjC/Mac/MPElementCollectionView.m | 6 +- .../ObjC/Mac/MPPasswordWindowController.m | 2 +- MasterPassword/ObjC/iOS/MPAvatarCell.h | 1 + MasterPassword/ObjC/iOS/MPAvatarCell.m | 47 +- .../ObjC/iOS/{PearlUIView.h => MPCell.h} | 10 +- .../iOS/{PearlUINavigationBar.h => MPCell.m} | 14 +- .../ObjC/iOS/MPCombinedViewController.m | 10 +- .../ObjC/iOS/MPElementListAllViewController.m | 11 +- .../ObjC/iOS/MPElementListSearchController.m | 6 +- .../ObjC/iOS/MPLogsViewController.m | 2 +- .../ObjC/iOS/MPMainViewController.m | 2 +- MasterPassword/ObjC/iOS/MPPasswordCell.h | 18 +- MasterPassword/ObjC/iOS/MPPasswordCell.m | 154 +-- .../ObjC/iOS/MPPasswordElementCell.h | 31 + .../ObjC/iOS/MPPasswordElementCell.m | 93 ++ .../ObjC/iOS/MPPasswordGeneratedCell.m | 87 -- MasterPassword/ObjC/iOS/MPPasswordLargeCell.h | 44 + MasterPassword/ObjC/iOS/MPPasswordLargeCell.m | 213 ++++ ...dCell.h => MPPasswordLargeGeneratedCell.h} | 11 +- .../ObjC/iOS/MPPasswordLargeGeneratedCell.m | 139 +++ ...oredCell.h => MPPasswordLargeStoredCell.h} | 10 +- .../ObjC/iOS/MPPasswordLargeStoredCell.m | 112 +++ MasterPassword/ObjC/iOS/MPPasswordSmallCell.h | 33 + MasterPassword/ObjC/iOS/MPPasswordSmallCell.m | 48 + .../ObjC/iOS/MPPasswordStoredCell.m | 73 -- MasterPassword/ObjC/iOS/MPPasswordTypesCell.h | 34 + MasterPassword/ObjC/iOS/MPPasswordTypesCell.m | 218 ++++ .../ObjC/iOS/MPPasswordsViewController.h | 3 +- .../ObjC/iOS/MPPasswordsViewController.m | 360 +++++-- .../ObjC/iOS/MPUnlockViewController.m | 15 +- .../ObjC/iOS/MPUsersViewController.m | 124 ++- MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 96 +- .../ObjC/iOS/MasterPassword-Info.plist | 7 +- .../project.pbxproj | 254 +++-- .../ObjC/iOS/PearlUINavigationBar.m | 43 - MasterPassword/ObjC/iOS/PearlUIView.m | 32 - MasterPassword/ObjC/iOS/Storyboard.storyboard | 940 +++++++----------- .../Resources/Media/Fonts/Exo-Bold.otf | Bin 111716 -> 0 bytes .../Resources/Media/Fonts/Exo-ExtraBold.otf | Bin 112380 -> 0 bytes .../Resources/Media/Fonts/Exo-Regular.otf | Bin 105348 -> 0 bytes .../Resources/Media/Fonts/Exo2.0-Bold.otf | Bin 0 -> 70756 bytes .../Media/Fonts/Exo2.0-ExtraBold.otf | Bin 0 -> 70888 bytes .../Resources/Media/Fonts/Exo2.0-Regular.otf | Bin 0 -> 67220 bytes .../Resources/Media/Fonts/Exo2.0-Thin.otf | Bin 0 -> 61260 bytes 52 files changed, 2059 insertions(+), 1357 deletions(-) delete mode 160000 External/FontReplacer rename MasterPassword/ObjC/iOS/{PearlUIView.h => MPCell.h} (75%) rename MasterPassword/ObjC/iOS/{PearlUINavigationBar.h => MPCell.m} (61%) create mode 100644 MasterPassword/ObjC/iOS/MPPasswordElementCell.h create mode 100644 MasterPassword/ObjC/iOS/MPPasswordElementCell.m delete mode 100644 MasterPassword/ObjC/iOS/MPPasswordGeneratedCell.m create mode 100644 MasterPassword/ObjC/iOS/MPPasswordLargeCell.h create mode 100644 MasterPassword/ObjC/iOS/MPPasswordLargeCell.m rename MasterPassword/ObjC/iOS/{MPPasswordGeneratedCell.h => MPPasswordLargeGeneratedCell.h} (63%) create mode 100644 MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.m rename MasterPassword/ObjC/iOS/{MPPasswordStoredCell.h => MPPasswordLargeStoredCell.h} (70%) create mode 100644 MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.m create mode 100644 MasterPassword/ObjC/iOS/MPPasswordSmallCell.h create mode 100644 MasterPassword/ObjC/iOS/MPPasswordSmallCell.m delete mode 100644 MasterPassword/ObjC/iOS/MPPasswordStoredCell.m create mode 100644 MasterPassword/ObjC/iOS/MPPasswordTypesCell.h create mode 100644 MasterPassword/ObjC/iOS/MPPasswordTypesCell.m delete mode 100644 MasterPassword/ObjC/iOS/PearlUINavigationBar.m delete mode 100644 MasterPassword/ObjC/iOS/PearlUIView.m delete mode 100644 MasterPassword/Resources/Media/Fonts/Exo-Bold.otf delete mode 100644 MasterPassword/Resources/Media/Fonts/Exo-ExtraBold.otf delete mode 100644 MasterPassword/Resources/Media/Fonts/Exo-Regular.otf create mode 100644 MasterPassword/Resources/Media/Fonts/Exo2.0-Bold.otf create mode 100644 MasterPassword/Resources/Media/Fonts/Exo2.0-ExtraBold.otf create mode 100644 MasterPassword/Resources/Media/Fonts/Exo2.0-Regular.otf create mode 100644 MasterPassword/Resources/Media/Fonts/Exo2.0-Thin.otf 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 02ff3b55f52b3ba17a8cea16b5bccde22b1b5f2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111716 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�HjB0Ed4Vob{aqfu@xVvARL6j&eMC`BBSsREyAm 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 X*HXACO>@km%lzu+KW1~2)%^beX$Ioz diff --git a/MasterPassword/Resources/Media/Fonts/Exo-ExtraBold.otf b/MasterPassword/Resources/Media/Fonts/Exo-ExtraBold.otf deleted file mode 100644 index 1e1c33115d1e31fcba436dbc6c42191a18532616..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112380 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
->z%7My&}2iR9F%cSk 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