Merge branch 'new-storyboard'
Conflicts: External/LoveLyndir External/Pearl
This commit is contained in:
@@ -37,6 +37,8 @@
|
||||
- (NSString *)shortNameOfType:(MPElementType)type;
|
||||
- (NSString *)classNameOfType:(MPElementType)type;
|
||||
- (Class)classOfType:(MPElementType)type;
|
||||
- (NSArray *)allTypes;
|
||||
- (NSArray *)allTypesStartingWith:(MPElementType)startingType;
|
||||
- (MPElementType)nextType:(MPElementType)type;
|
||||
- (MPElementType)previousType:(MPElementType)type;
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV0
|
||||
@@ -31,6 +31,21 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return strf( @"<%@: version=%lu>", NSStringFromClass( [self class] ), (unsigned long)self.version );
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
|
||||
if (other == self)
|
||||
return YES;
|
||||
if (!other || ![other conformsToProtocol:@protocol(MPAlgorithm)])
|
||||
return NO;
|
||||
|
||||
return [(id<MPAlgorithm>)other version] == [self version];
|
||||
}
|
||||
|
||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
||||
|
||||
NSError *error = nil;
|
||||
@@ -38,7 +53,7 @@
|
||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
||||
NSArray *migrationElements = [moc executeFetchRequest:migrationRequest error:&error];
|
||||
if (!migrationElements) {
|
||||
err(@"While looking for elements to migrate: %@", error);
|
||||
err( @"While looking for elements to migrate: %@", error );
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -53,7 +68,7 @@
|
||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
||||
|
||||
if (element.version != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
if (!explicit) {
|
||||
@@ -70,18 +85,19 @@
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
|
||||
|
||||
uint32_t nuserNameLength = htonl(userName.length);
|
||||
uint32_t nuserNameLength = htonl( userName.length );
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *keyData = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nuserNameLength
|
||||
length:sizeof(nuserNameLength)],
|
||||
[userName dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nuserNameLength
|
||||
length:sizeof( nuserNameLength )],
|
||||
[userName dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
MPKey *key = [self keyFromKeyData:keyData];
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex],
|
||||
-[start timeIntervalSinceNow] );
|
||||
|
||||
return key;
|
||||
}
|
||||
@@ -127,7 +143,7 @@
|
||||
return @"Device Private Password";
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %lu", (long)type);
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
}
|
||||
|
||||
- (NSString *)shortNameOfType:(MPElementType)type {
|
||||
@@ -161,7 +177,7 @@
|
||||
return @"Device";
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %lu", (long)type);
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
}
|
||||
|
||||
- (NSString *)classNameOfType:(MPElementType)type {
|
||||
@@ -172,7 +188,7 @@
|
||||
- (Class)classOfType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
Throw(@"No type given.");
|
||||
Throw( @"No type given." );
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
@@ -200,14 +216,27 @@
|
||||
return [MPElementStoredEntity class];
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %lu", (long)type);
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
}
|
||||
|
||||
- (NSArray *)allTypes {
|
||||
|
||||
return [self allTypesStartingWith:MPElementTypeGeneratedMaximum];
|
||||
}
|
||||
|
||||
- (NSArray *)allTypesStartingWith:(MPElementType)startingType {
|
||||
|
||||
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
||||
MPElementType currentType = startingType;
|
||||
do {
|
||||
[allTypes addObject:@(currentType)];
|
||||
} while ((currentType = [self nextType:currentType]) != startingType);
|
||||
|
||||
return allTypes;
|
||||
}
|
||||
|
||||
- (MPElementType)nextType:(MPElementType)type {
|
||||
|
||||
if (!type)
|
||||
Throw(@"No type given.");
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return MPElementTypeStoredDevicePrivate;
|
||||
@@ -225,9 +254,9 @@
|
||||
return MPElementTypeGeneratedPIN;
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return MPElementTypeStoredPersonal;
|
||||
default:
|
||||
return MPElementTypeGeneratedLong;
|
||||
}
|
||||
|
||||
Throw(@"Type not supported: %lu", (long)type);
|
||||
}
|
||||
|
||||
- (MPElementType)previousType:(MPElementType)type {
|
||||
@@ -247,38 +276,38 @@
|
||||
[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]];
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
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]);
|
||||
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] );
|
||||
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]);
|
||||
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 *cipher = typeCiphers[htons(seedBytes[0]) % [typeCiphers count]];
|
||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
||||
NSAssert( [seed length], @"Missing seed." );
|
||||
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: %@", 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.");
|
||||
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = htons(seedBytes[c + 1]);
|
||||
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);
|
||||
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
@@ -292,7 +321,7 @@
|
||||
|
||||
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
@@ -300,13 +329,14 @@
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN: {
|
||||
NSAssert(NO, @"Cannot save content to element with generated type %lu.", (long)element.type);
|
||||
NSAssert( NO, @"Cannot save content to element with generated type %lu.", (long)element.type );
|
||||
break;
|
||||
}
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
@@ -314,8 +344,9 @@
|
||||
break;
|
||||
}
|
||||
case MPElementTypeStoredDevicePrivate: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
|
||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
@@ -349,9 +380,9 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void (^)(NSString *result))resultBlock {
|
||||
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
@@ -359,17 +390,18 @@
|
||||
case MPElementTypeGeneratedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
case MPElementTypeGeneratedPIN: {
|
||||
NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]],
|
||||
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementGeneratedEntity class]],
|
||||
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
|
||||
NSString *name = element.name;
|
||||
MPElementType type = element.type;
|
||||
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
|
||||
id<MPAlgorithm> algorithm = nil;
|
||||
if (!element.name.length)
|
||||
err(@"Missing name.");
|
||||
err( @"Missing name." );
|
||||
else if (!elementKey.keyData.length)
|
||||
err(@"Missing key.");
|
||||
err( @"Missing key." );
|
||||
else
|
||||
algorithm = element.algorithm;
|
||||
|
||||
@@ -381,8 +413,9 @@
|
||||
}
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
|
||||
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
|
||||
|
||||
@@ -393,8 +426,9 @@
|
||||
break;
|
||||
}
|
||||
case MPElementTypeStoredDevicePrivate: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
|
||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
|
||||
@@ -411,7 +445,7 @@
|
||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
@@ -422,8 +456,9 @@
|
||||
break;
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
if ([importKey.keyID isEqualToData:elementKey.keyID])
|
||||
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
|
||||
|
||||
@@ -441,7 +476,7 @@
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
||||
switch (element.type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
case MPElementTypeGeneratedLong:
|
||||
@@ -463,7 +498,7 @@
|
||||
|
||||
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||
|
||||
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
||||
if (!(element.type & MPElementFeatureExportContent))
|
||||
return nil;
|
||||
|
||||
@@ -480,8 +515,9 @@
|
||||
}
|
||||
|
||||
case MPElementTypeStoredPersonal: {
|
||||
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
|
||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
||||
[element class] );
|
||||
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
|
||||
break;
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:@{
|
||||
(__bridge id)kSecAttrService : @"Saved Master Password",
|
||||
(__bridge id)kSecAttrAccount : IfNotNilElse(user.name, @"")
|
||||
(__bridge id)kSecAttrAccount : IfNotNilElse( user.name, @"" )
|
||||
}
|
||||
matches:nil];
|
||||
}
|
||||
@@ -25,11 +25,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
|
||||
if (!keyData) {
|
||||
inf(@"No key found in keychain for: %@", user.userID);
|
||||
inf( @"No key found in keychain for: %@", user.userID );
|
||||
return nil;
|
||||
}
|
||||
|
||||
inf(@"Found key in keychain for: %@", user.userID);
|
||||
inf( @"Found key in keychain for: %@", user.userID );
|
||||
return [MPAlgorithmDefault keyFromKeyData:keyData];
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
NSData *existingKeyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )];
|
||||
|
||||
if (![existingKeyData isEqualToData:self.key.keyData]) {
|
||||
inf(@"Saving key in keychain for: %@", user.userID);
|
||||
inf( @"Saving key in keychain for: %@", user.userID );
|
||||
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery( user )
|
||||
withAttributes:@{
|
||||
@@ -56,7 +56,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )];
|
||||
if (result == noErr) {
|
||||
inf(@"Removed key from keychain for: %@", user.userID);
|
||||
inf( @"Removed key from keychain for: %@", user.userID );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
|
||||
}
|
||||
@@ -74,7 +74,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password {
|
||||
|
||||
if (password)
|
||||
NSAssert(![NSThread isMainThread], @"Computing key must not happen from the main thread.");
|
||||
NSAssert( ![NSThread isMainThread], @"Computing key must not happen from the main thread." );
|
||||
if (!user)
|
||||
return NO;
|
||||
|
||||
@@ -92,14 +92,14 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
|
||||
if (!user.saveKey)
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
[self forgetSavedKeyFor:user];
|
||||
|
||||
else if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:tryKey.keyID]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
inf(@"Saved password doesn't match keyID for: %@", user.userID);
|
||||
inf( @"Saved password doesn't match keyID for: %@", user.userID );
|
||||
|
||||
tryKey = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
@@ -110,7 +110,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
if (!tryKey) {
|
||||
if ([password length]) if ((tryKey = [MPAlgorithmDefault keyForPassword:password
|
||||
ofUserNamed:user.name])) if (![user.keyID isEqual:tryKey.keyID]) {
|
||||
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
|
||||
inf( @"Key derived from password doesn't match keyID for: %@", user.userID );
|
||||
|
||||
tryKey = nil;
|
||||
}
|
||||
@@ -119,13 +119,13 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
// No more methods left, fail if key still not known.
|
||||
if (!tryKey) {
|
||||
if (password) {
|
||||
inf(@"Login failed for: %@", user.userID);
|
||||
inf( @"Login failed for: %@", user.userID );
|
||||
MPCheckpoint( MPCheckpointSignInFailed, nil );
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
inf(@"Logged in: %@", user.userID);
|
||||
inf( @"Logged in: %@", user.userID );
|
||||
|
||||
if (![self.key isEqualToKey:tryKey]) {
|
||||
self.key = tryKey;
|
||||
@@ -147,13 +147,19 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"While setting username: %@", exception);
|
||||
err( @"While setting username: %@", exception );
|
||||
}
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
[moc saveToStore];
|
||||
self.activeUser = user;
|
||||
|
||||
// Perform a data sanity check now that we're logged in as the user to allow fixes that require the user's key.
|
||||
if ([[MPConfig get].checkInconsistency boolValue])
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
[self findAndFixInconsistenciesSaveInContext:context];
|
||||
}];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self];
|
||||
MPCheckpoint( MPCheckpointSignedIn, nil );
|
||||
|
||||
@@ -163,12 +169,13 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
- (void)migrateElementsForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
|
||||
|
||||
if (![user.elements count])
|
||||
// Nothing to migrate.
|
||||
// Nothing to migrate.
|
||||
return;
|
||||
|
||||
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) {
|
||||
@@ -183,12 +190,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
dispatch_group_enter( recoverPasswordGroup );
|
||||
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
||||
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
|
||||
element.name )
|
||||
element.name )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
@try {
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
// Don't Migrate
|
||||
// Don't Migrate
|
||||
return;
|
||||
|
||||
masterPassword = [alert_ textFieldAtIndex:0].text;
|
||||
@@ -200,14 +207,14 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
|
||||
#endif
|
||||
if (!masterPassword)
|
||||
// Don't Migrate
|
||||
// Don't Migrate
|
||||
break;
|
||||
|
||||
recoverKey = [element.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
|
||||
}
|
||||
|
||||
if (!content)
|
||||
// Don't Migrate
|
||||
// Don't Migrate
|
||||
break;
|
||||
|
||||
if (![recoverKey isEqualToKey:newKey])
|
||||
|
@@ -16,11 +16,12 @@
|
||||
#endif
|
||||
|
||||
@property(strong, nonatomic) MPKey *key;
|
||||
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
|
||||
|
||||
+ (instancetype)get;
|
||||
|
||||
- (MPUserEntity *)activeUserForMainThread;
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc;
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
|
||||
- (void)setActiveUser:(MPUserEntity *)activeUser;
|
||||
|
||||
@end
|
||||
|
@@ -10,9 +10,7 @@
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared {
|
||||
NSManagedObjectID *_activeUserOID;
|
||||
}
|
||||
@implementation MPAppDelegate_Shared
|
||||
|
||||
+ (MPAppDelegate_Shared *)get {
|
||||
|
||||
@@ -30,17 +28,15 @@
|
||||
return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)moc {
|
||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
if (!_activeUserOID || !moc)
|
||||
NSManagedObjectID *activeUserOID = self.activeUserOID;
|
||||
if (!activeUserOID || !context)
|
||||
return nil;
|
||||
|
||||
NSError *error;
|
||||
MPUserEntity *activeUser = (MPUserEntity *)[moc existingObjectWithID:_activeUserOID error:&error];
|
||||
if (!activeUser) {
|
||||
MPUserEntity *activeUser = [MPUserEntity existingObjectWithID:activeUserOID inContext:context];
|
||||
if (!activeUser)
|
||||
[self signOutAnimated:YES];
|
||||
err(@"Failed to retrieve active user: %@", error);
|
||||
}
|
||||
|
||||
return activeUser;
|
||||
}
|
||||
@@ -51,7 +47,7 @@
|
||||
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
|
||||
err(@"Failed to obtain a permanent object ID after setting active user: %@", error);
|
||||
|
||||
_activeUserOID = activeUser.objectID;
|
||||
self.activeUserOID = activeUser.objectID;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
#import "UbiquityStoreManager.h"
|
||||
#import "MPFixable.h"
|
||||
|
||||
typedef enum {
|
||||
MPImportResultSuccess,
|
||||
@@ -21,17 +22,20 @@ typedef enum {
|
||||
@interface MPAppDelegate_Shared(Store)<UbiquityStoreManagerDelegate>
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady;
|
||||
+ (BOOL)managedObjectContextForMainThreadPerformBlock:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
|
||||
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void (^)(NSManagedObjectContext *mainContext))mocBlock;
|
||||
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock;
|
||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
|
||||
|
||||
- (UbiquityStoreManager *)storeManager;
|
||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
||||
|
||||
/** @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;
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
|
||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords;
|
||||
|
||||
@end
|
||||
|
@@ -18,13 +18,13 @@
|
||||
#define MPMigrationLevelLocalStoreKey @"MPMigrationLevelLocalStoreKey"
|
||||
#define MPMigrationLevelCloudStoreKey @"MPMigrationLevelCloudStoreKey"
|
||||
|
||||
typedef NS_ENUM(NSInteger, MPMigrationLevelLocalStore) {
|
||||
typedef NS_ENUM( NSInteger, MPMigrationLevelLocalStore ) {
|
||||
MPMigrationLevelLocalStoreV1,
|
||||
MPMigrationLevelLocalStoreV2,
|
||||
MPMigrationLevelLocalStoreCurrent = MPMigrationLevelLocalStoreV2,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, MPMigrationLevelCloudStore) {
|
||||
typedef NS_ENUM( NSInteger, MPMigrationLevelCloudStore ) {
|
||||
MPMigrationLevelCloudStoreV1,
|
||||
MPMigrationLevelCloudStoreV2,
|
||||
MPMigrationLevelCloudStoreV3,
|
||||
@@ -32,15 +32,18 @@ typedef NS_ENUM(NSInteger, MPMigrationLevelCloudStore) {
|
||||
};
|
||||
|
||||
@implementation MPAppDelegate_Shared(Store)
|
||||
PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext);
|
||||
PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext);
|
||||
|
||||
PearlAssociatedObjectProperty( id, SaveObserver, saveObserver );
|
||||
|
||||
PearlAssociatedObjectProperty( NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext );
|
||||
|
||||
PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext );
|
||||
|
||||
#pragma mark - Core Data setup
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
|
||||
|
||||
NSAssert([[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread.");
|
||||
NSAssert( [[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread." );
|
||||
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
||||
if (!mainManagedObjectContext || ![[NSThread currentThread] isMainThread])
|
||||
return nil;
|
||||
@@ -48,14 +51,40 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
return mainManagedObjectContext;
|
||||
}
|
||||
|
||||
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock {
|
||||
+ (BOOL)managedObjectContextForMainThreadPerformBlock:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock {
|
||||
|
||||
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
||||
if (!mainManagedObjectContext)
|
||||
return NO;
|
||||
|
||||
[mainManagedObjectContext performBlock:^{
|
||||
mocBlock( mainManagedObjectContext );
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)managedObjectContextForMainThreadPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *mainContext))mocBlock {
|
||||
|
||||
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
||||
if (!mainManagedObjectContext)
|
||||
return NO;
|
||||
|
||||
[mainManagedObjectContext performBlockAndWait:^{
|
||||
mocBlock( mainManagedObjectContext );
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)managedObjectContextPerformBlock:(void ( ^ )(NSManagedObjectContext *context))mocBlock {
|
||||
|
||||
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
|
||||
if (!privateManagedObjectContextIfReady)
|
||||
return NO;
|
||||
|
||||
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
moc.parentContext = mainManagedObjectContext;
|
||||
moc.parentContext = privateManagedObjectContextIfReady;
|
||||
[moc performBlock:^{
|
||||
mocBlock( moc );
|
||||
}];
|
||||
@@ -63,14 +92,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock {
|
||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void ( ^ )(NSManagedObjectContext *context))mocBlock {
|
||||
|
||||
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
||||
if (!mainManagedObjectContext)
|
||||
NSManagedObjectContext *privateManagedObjectContextIfReady = [[self get] privateManagedObjectContextIfReady];
|
||||
if (!privateManagedObjectContextIfReady)
|
||||
return NO;
|
||||
|
||||
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
moc.parentContext = mainManagedObjectContext;
|
||||
moc.parentContext = privateManagedObjectContextIfReady;
|
||||
[moc performBlockAndWait:^{
|
||||
mocBlock( moc );
|
||||
}];
|
||||
@@ -105,14 +134,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
[[self mainManagedObjectContext] saveToStore];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
@@ -124,6 +153,41 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
return storeManager;
|
||||
}
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest new];
|
||||
fetchRequest.fetchBatchSize = 50;
|
||||
|
||||
MPFixableResult result = MPFixableResultNoProblems;
|
||||
for (NSEntityDescription *entity in [context.persistentStoreCoordinator.managedObjectModel entities])
|
||||
if (class_conformsToProtocol( NSClassFromString( entity.managedObjectClassName ), @protocol(MPFixable) )) {
|
||||
fetchRequest.entity = entity;
|
||||
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
|
||||
if (!objects) {
|
||||
err( @"Failed to fetch %@ objects: %@", entity, error );
|
||||
continue;
|
||||
}
|
||||
|
||||
for (NSManagedObject<MPFixable> *object in objects)
|
||||
result = MPApplyFix( result, ^MPFixableResult {
|
||||
return [object findAndFixInconsistenciesInContext:context];
|
||||
} );
|
||||
}
|
||||
|
||||
if (result == MPFixableResultNoProblems)
|
||||
inf( @"Sanity check found no problems in store." );
|
||||
|
||||
else {
|
||||
[context saveToStore];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPFoundInconsistenciesNotification object:nil userInfo:@{
|
||||
MPInconsistenciesFixResultUserKey : @(result)
|
||||
}];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)migrateStoreForManager:(UbiquityStoreManager *)manager isCloud:(BOOL)isCloudStore {
|
||||
|
||||
[self migrateLocalStore];
|
||||
@@ -136,42 +200,42 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
MPMigrationLevelLocalStore migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
|
||||
if (migrationLevel >= MPMigrationLevelLocalStoreCurrent)
|
||||
// Local store up-to-date.
|
||||
// Local store up-to-date.
|
||||
return;
|
||||
|
||||
inf(@"Local store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelLocalStoreCurrent);
|
||||
inf( @"Local store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelLocalStoreCurrent );
|
||||
if (migrationLevel <= MPMigrationLevelLocalStoreV1) if (![self migrateV1LocalStore]) {
|
||||
inf(@"Failed to migrate old V1 to new local store.");
|
||||
inf( @"Failed to migrate old V1 to new local store." );
|
||||
return;
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:MPMigrationLevelLocalStoreCurrent forKey:MPMigrationLevelLocalStoreKey];
|
||||
inf(@"Successfully migrated old to new local store.");
|
||||
inf( @"Successfully migrated old to new local store." );
|
||||
}
|
||||
|
||||
- (void)migrateCloudStore {
|
||||
|
||||
MPMigrationLevelCloudStore migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelCloudStoreKey];
|
||||
if (migrationLevel >= MPMigrationLevelCloudStoreCurrent)
|
||||
// Cloud store up-to-date.
|
||||
// Cloud store up-to-date.
|
||||
return;
|
||||
|
||||
inf(@"Cloud store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelCloudStoreCurrent);
|
||||
inf( @"Cloud store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelCloudStoreCurrent );
|
||||
if (migrationLevel <= MPMigrationLevelCloudStoreV1) {
|
||||
if (![self migrateV1CloudStore]) {
|
||||
inf(@"Failed to migrate old V1 to new cloud store.");
|
||||
inf( @"Failed to migrate old V1 to new cloud store." );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (migrationLevel <= MPMigrationLevelCloudStoreV2) {
|
||||
if (![self migrateV2CloudStore]) {
|
||||
inf(@"Failed to migrate old V2 to new cloud store.");
|
||||
inf( @"Failed to migrate old V2 to new cloud store." );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:MPMigrationLevelCloudStoreCurrent forKey:MPMigrationLevelCloudStoreKey];
|
||||
inf(@"Successfully migrated old to new cloud store.");
|
||||
inf( @"Successfully migrated old to new cloud store." );
|
||||
}
|
||||
|
||||
- (BOOL)migrateV1CloudStore {
|
||||
@@ -184,11 +248,11 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
// Migrate cloud store.
|
||||
NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"];
|
||||
if (!uuid) {
|
||||
inf(@"No V1 cloud store to migrate.");
|
||||
inf( @"No V1 cloud store to migrate." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
inf(@"Migrating V1 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"]);
|
||||
inf( @"Migrating V1 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"] );
|
||||
NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:MPCloudContainerIdentifier];
|
||||
NSURL *oldCloudContentURL = [[cloudContainerURL
|
||||
URLByAppendingPathComponent:@"Data" isDirectory:YES]
|
||||
@@ -205,11 +269,11 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
// Migrate cloud store.
|
||||
NSString *uuid = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:@"USMStoreUUIDKey"];
|
||||
if (!uuid) {
|
||||
inf(@"No V2 cloud store to migrate.");
|
||||
inf( @"No V2 cloud store to migrate." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
inf(@"Migrating V2 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"]);
|
||||
inf( @"Migrating V2 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"] );
|
||||
NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:MPCloudContainerIdentifier];
|
||||
NSURL *oldCloudContentURL = [[cloudContainerURL
|
||||
URLByAppendingPathComponent:@"CloudLogs" isDirectory:YES]
|
||||
@@ -228,11 +292,11 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
NSURL *oldLocalStoreURL = [[applicationFilesDirectory
|
||||
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO]) {
|
||||
inf(@"No V1 local store to migrate.");
|
||||
inf( @"No V1 local store to migrate." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
inf(@"Migrating V1 local store");
|
||||
inf( @"Migrating V1 local store" );
|
||||
return [self migrateFromLocalStore:oldLocalStoreURL];
|
||||
}
|
||||
|
||||
@@ -240,7 +304,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
NSURL *newLocalStoreURL = [self.storeManager URLForLocalStore];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) {
|
||||
wrn(@"Can't migrate local store: A new local store already exists.");
|
||||
wrn( @"Can't migrate local store: A new local store already exists." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -251,14 +315,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
return NO;
|
||||
}
|
||||
|
||||
inf(@"Successfully migrated to new local store.");
|
||||
inf( @"Successfully migrated to new local store." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)migrateFromCloudStore:(NSURL *)oldCloudStoreURL cloudContent:(NSURL *)oldCloudContentURL {
|
||||
|
||||
if (![self.storeManager cloudSafeForSeeding]) {
|
||||
inf(@"Can't migrate cloud store: A new cloud store already exists.");
|
||||
inf( @"Can't migrate cloud store: A new cloud store already exists." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -268,7 +332,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
strategy:0 error:nil cause:nil context:nil])
|
||||
return NO;
|
||||
|
||||
inf(@"Successfully migrated to new cloud store.");
|
||||
inf( @"Successfully migrated to new cloud store." );
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -282,7 +346,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
|
||||
|
||||
inf(@"[StoreManager] %@", message);
|
||||
inf( @"[StoreManager] %@", message );
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
|
||||
@@ -292,6 +356,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;
|
||||
}];
|
||||
@@ -302,14 +371,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
|
||||
isCloud:(BOOL)isCloudStore {
|
||||
|
||||
inf(@"Using iCloud? %@", @(isCloudStore));
|
||||
inf( @"Using iCloud? %@", @(isCloudStore) );
|
||||
MPCheckpoint( MPCheckpointCloud, @{
|
||||
@"enabled" : @(isCloudStore)
|
||||
} );
|
||||
|
||||
// Create our contexts.
|
||||
NSManagedObjectContext
|
||||
*privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
NSManagedObjectContext *privateManagedObjectContext =
|
||||
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
[privateManagedObjectContext performBlockAndWait:^{
|
||||
privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||
privateManagedObjectContext.persistentStoreCoordinator = coordinator;
|
||||
@@ -332,25 +401,42 @@ 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) {
|
||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||
[mainManagedObjectContext performBlock:^{
|
||||
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
||||
}];
|
||||
}];
|
||||
|
||||
self.privateManagedObjectContext = privateManagedObjectContext;
|
||||
self.mainManagedObjectContext = mainManagedObjectContext;
|
||||
|
||||
// Perform a data sanity check on the newly loaded store to find and fix any issues.
|
||||
if ([[MPConfig get].checkInconsistency boolValue])
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
[self findAndFixInconsistenciesSaveInContext:context];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
|
||||
context:(id)context {
|
||||
|
||||
err(@"[StoreManager] ERROR: cause=%@, context=%@, error=%@", NSStringFromUSMCause( cause ), context, error);
|
||||
err( @"[StoreManager] ERROR: cause=%@, context=%@, error=%@", NSStringFromUSMCause( cause ), context, error );
|
||||
MPCheckpoint( MPCheckpointMPErrorUbiquity, @{
|
||||
@"cause" : @(cause),
|
||||
@"error.code" : @(error.code),
|
||||
@"error.domain" : NilToNSNull(error.domain),
|
||||
@"error.reason" : NilToNSNull(IfNotNilElse( [error localizedFailureReason], [error localizedDescription] )),
|
||||
@"error.domain" : NilToNSNull( error.domain ),
|
||||
@"error.reason" : NilToNSNull( IfNotNilElse( [error localizedFailureReason], [error localizedDescription] ) ),
|
||||
} );
|
||||
}
|
||||
|
||||
#pragma mark - Utilities
|
||||
|
||||
- (void)addElementNamed:(NSString *)siteName completion:(void (^)(MPElementEntity *element))completion {
|
||||
- (void)addElementNamed:(NSString *)siteName completion:(void ( ^ )(MPElementEntity *element))completion {
|
||||
|
||||
if (![siteName length]) {
|
||||
completion( nil );
|
||||
@@ -359,47 +445,46 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [self activeUserInContext:context];
|
||||
NSAssert(activeUser, @"Missing user.");
|
||||
if (!activeUser)
|
||||
NSAssert( activeUser, @"Missing user." );
|
||||
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
|
||||
inManagedObjectContext:context];
|
||||
NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
|
||||
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||
element.name = siteName;
|
||||
element.user = activeUser;
|
||||
element.type = type;
|
||||
element.lastUsed = [NSDate date];
|
||||
element.version = MPAlgorithmDefaultVersion;
|
||||
[context saveToStore];
|
||||
|
||||
NSError *error = nil;
|
||||
if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error])
|
||||
err(@"Failed to obtain a permanent object ID after creating new element: %@", 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] );
|
||||
} );
|
||||
[context saveToStore];
|
||||
|
||||
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.
|
||||
MPElementEntity *newElement
|
||||
= [NSEntityDescription insertNewObjectForEntityForName:[element.algorithm classNameOfType:type]
|
||||
inManagedObjectContext:context];
|
||||
NSString *typeEntityName = [element.algorithm classNameOfType:type];
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||
newElement.type = type;
|
||||
newElement.name = element.name;
|
||||
newElement.user = element.user;
|
||||
@@ -408,15 +493,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
newElement.version = element.version;
|
||||
newElement.loginName = element.loginName;
|
||||
|
||||
NSError *error = nil;
|
||||
if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error])
|
||||
err( @"Failed to obtain a permanent object ID after changing object type: %@", error );
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
@@ -425,10 +509,10 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
}
|
||||
|
||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
||||
askImportPassword:(NSString *(^)(NSString *userName))importPassword
|
||||
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
|
||||
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
|
||||
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
|
||||
|
||||
NSAssert(![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread.");
|
||||
NSAssert( ![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread." );
|
||||
|
||||
__block MPImportResult result = MPImportResultCancelled;
|
||||
do {
|
||||
@@ -444,8 +528,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
}
|
||||
|
||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
||||
askImportPassword:(NSString *(^)(NSString *userName))askImportPassword
|
||||
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
|
||||
askImportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
|
||||
askUserPassword:(NSString *( ^ )(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))askUserPassword
|
||||
saveInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
// Compile patterns.
|
||||
@@ -456,7 +540,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:(NSRegularExpressionOptions)0 error:&error];
|
||||
if (error) {
|
||||
err(@"Error loading the header pattern: %@", error);
|
||||
err( @"Error loading the header pattern: %@", error );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
}
|
||||
@@ -465,13 +549,13 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:(NSRegularExpressionOptions)0 error:&error];
|
||||
if (error) {
|
||||
err(@"Error loading the site pattern: %@", error);
|
||||
err( @"Error loading the site pattern: %@", error );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse import data.
|
||||
inf(@"Importing sites.");
|
||||
inf( @"Importing sites." );
|
||||
__block MPUserEntity *user = nil;
|
||||
id<MPAlgorithm> importAlgorithm = nil;
|
||||
NSString *importBundleVersion = nil, *importUserName = nil;
|
||||
@@ -499,7 +583,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
// Header
|
||||
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
|
||||
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
|
||||
err(@"Invalid header format in line: %@", importedSiteLine);
|
||||
err( @"Invalid header format in line: %@", importedSiteLine );
|
||||
return MPImportResultMalformedInput;
|
||||
}
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
||||
@@ -513,16 +597,16 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
||||
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
||||
if (!users) {
|
||||
err(@"While looking for user: %@, error: %@", importUserName, error);
|
||||
err( @"While looking for user: %@, error: %@", importUserName, error );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
if ([users count] > 1) {
|
||||
err(@"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count]);
|
||||
err( @"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count] );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
|
||||
user = [users count]? [users lastObject]: nil;
|
||||
dbg(@"Found user: %@", [user debugDescription]);
|
||||
dbg( @"Found user: %@", [user debugDescription] );
|
||||
}
|
||||
if ([headerName isEqualToString:@"Key ID"])
|
||||
importKeyID = [headerValue decodeHex];
|
||||
@@ -547,7 +631,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
// Site
|
||||
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
|
||||
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
|
||||
err(@"Invalid site format in line: %@", importedSiteLine);
|
||||
err( @"Invalid site format in line: %@", importedSiteLine );
|
||||
return MPImportResultMalformedInput;
|
||||
}
|
||||
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
||||
@@ -566,25 +650,26 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
|
||||
NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error];
|
||||
if (!existingSites) {
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
|
||||
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error );
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
if ([existingSites count]) {
|
||||
dbg(@"Existing sites: %@", existingSites);
|
||||
dbg( @"Existing sites: %@", existingSites );
|
||||
[elementsToDelete addObjectsFromArray:existingSites];
|
||||
}
|
||||
}
|
||||
[importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]];
|
||||
dbg(@"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@",
|
||||
lastUsed, uses, type, version, name, exportContent);
|
||||
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@",
|
||||
lastUsed, uses, type, version, name, exportContent );
|
||||
}
|
||||
|
||||
// Ask for confirmation to import these sites and the master password of the user.
|
||||
inf(@"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count], (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName]);
|
||||
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count],
|
||||
(unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] );
|
||||
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
|
||||
[elementsToDelete count] );
|
||||
if (!userMasterPassword) {
|
||||
inf(@"Import cancelled.");
|
||||
inf( @"Import cancelled." );
|
||||
return MPImportResultCancelled;
|
||||
}
|
||||
MPKey *userKey = [MPAlgorithmDefault keyForPassword:userMasterPassword ofUserNamed:user? user.name: importUserName];
|
||||
@@ -600,17 +685,16 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
// Delete existing sites.
|
||||
if (elementsToDelete.count)
|
||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
||||
inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
|
||||
[context deleteObject:obj];
|
||||
}];
|
||||
|
||||
// Make sure there is a user.
|
||||
if (!user) {
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
||||
inManagedObjectContext:context];
|
||||
user = [MPUserEntity insertNewObjectInContext:context];
|
||||
user.name = importUserName;
|
||||
user.keyID = importKeyID;
|
||||
dbg(@"Created User: %@", [user debugDescription]);
|
||||
dbg( @"Created User: %@", [user debugDescription] );
|
||||
}
|
||||
|
||||
// Import new sites.
|
||||
@@ -623,9 +707,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
NSString *exportContent = siteElements[5];
|
||||
|
||||
// Create new site.
|
||||
MPElementEntity
|
||||
*element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( version ) classNameOfType:type]
|
||||
inManagedObjectContext:context];
|
||||
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
@@ -639,13 +722,13 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
[element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
|
||||
}
|
||||
|
||||
dbg(@"Created Element: %@", [element debugDescription]);
|
||||
dbg( @"Created Element: %@", [element debugDescription] );
|
||||
}
|
||||
|
||||
if (![context saveToStore])
|
||||
return MPImportResultInternalError;
|
||||
|
||||
inf(@"Import completed successfully.");
|
||||
inf( @"Import completed successfully." );
|
||||
MPCheckpoint( MPCheckpointSitesImported, nil );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
|
||||
@@ -655,15 +738,15 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
return MPImportResultSuccess;
|
||||
}
|
||||
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
||||
- (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords {
|
||||
|
||||
MPUserEntity *activeUser = [self activeUserForMainThread];
|
||||
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", activeUser.userID);
|
||||
inf( @"Exporting sites, %@, for: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID );
|
||||
|
||||
// Header.
|
||||
NSMutableString *export = [NSMutableString new];
|
||||
[export appendFormat:@"# Master Password site export\n"];
|
||||
if (showPasswords)
|
||||
if (revealPasswords)
|
||||
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
|
||||
else
|
||||
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
|
||||
@@ -673,7 +756,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
[export appendFormat:@"# User Name: %@\n", activeUser.name];
|
||||
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
|
||||
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
|
||||
if (showPasswords)
|
||||
if (revealPasswords)
|
||||
[export appendFormat:@"# Passwords: VISIBLE\n"];
|
||||
else
|
||||
[export appendFormat:@"# Passwords: PROTECTED\n"];
|
||||
@@ -693,7 +776,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
||||
|
||||
// Determine the content to export.
|
||||
if (!(type & MPElementFeatureDevicePrivate)) {
|
||||
if (showPasswords)
|
||||
if (revealPasswords)
|
||||
content = [element.algorithm resolveContentForElement:element usingKey:self.key];
|
||||
else if (type & MPElementFeatureExportContent)
|
||||
content = [element.algorithm exportContentForElement:element usingKey:self.key];
|
||||
@@ -702,11 +785,11 @@ 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, @{
|
||||
@"showPasswords" : @(showPasswords)
|
||||
@"showPasswords" : @(revealPasswords)
|
||||
} );
|
||||
|
||||
return export;
|
||||
|
@@ -14,5 +14,6 @@
|
||||
@property(nonatomic, retain) NSNumber *rememberLogin;
|
||||
|
||||
@property(nonatomic, retain) NSNumber *iCloudDecided;
|
||||
@property(nonatomic, retain) NSNumber *checkInconsistency;
|
||||
|
||||
@end
|
||||
|
@@ -6,12 +6,11 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPConfig.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@implementation MPConfig
|
||||
|
||||
@dynamic sendInfo, rememberLogin, iCloudDecided;
|
||||
@dynamic sendInfo, rememberLogin, iCloudDecided, checkInconsistency;
|
||||
|
||||
- (id)init {
|
||||
|
||||
@@ -19,11 +18,12 @@
|
||||
return nil;
|
||||
|
||||
[self.defaults registerDefaults:@{
|
||||
NSStringFromSelector( @selector(askForReviews) ) : @YES,
|
||||
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
|
||||
|
||||
NSStringFromSelector( @selector(sendInfo) ) : @NO,
|
||||
NSStringFromSelector( @selector(rememberLogin) ) : @NO,
|
||||
NSStringFromSelector( @selector(iCloudDecided) ) : @NO
|
||||
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
|
||||
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
|
||||
NSStringFromSelector( @selector( iCloudDecided ) ) : @NO,
|
||||
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO
|
||||
}];
|
||||
|
||||
self.delegate = [MPAppDelegate_Shared get];
|
||||
|
@@ -8,10 +8,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "MPFixable.h"
|
||||
|
||||
@class MPUserEntity;
|
||||
|
||||
@interface MPElementEntity : NSManagedObject
|
||||
@interface MPElementEntity : NSManagedObject <MPFixable>
|
||||
|
||||
@property(nonatomic, retain) NSDate *lastUsed;
|
||||
@property(nonatomic, retain) NSString *loginName;
|
||||
|
@@ -19,4 +19,9 @@
|
||||
@dynamic version_;
|
||||
@dynamic user;
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
return MPFixableResultNoProblems;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -7,9 +7,49 @@
|
||||
//
|
||||
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@implementation MPElementGeneratedEntity
|
||||
|
||||
@dynamic counter_;
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
|
||||
|
||||
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
||||
// Invalid self.type
|
||||
result = MPApplyFix( result, ^MPFixableResult {
|
||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||
self.name, self.user.name, (long)self.type, (long)self.user.defaultType );
|
||||
self.type = self.user.defaultType;
|
||||
return MPFixableResultProblemsFixed;
|
||||
} );
|
||||
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
||||
// Invalid self.user.defaultType
|
||||
result = MPApplyFix( result, ^MPFixableResult {
|
||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||
self.name, self.user.name, (long)self.type, (long)MPElementTypeGeneratedLong );
|
||||
self.type = MPElementTypeGeneratedLong;
|
||||
return MPFixableResultProblemsFixed;
|
||||
} );
|
||||
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
||||
// Mismatch between self.type and self.class
|
||||
result = MPApplyFix( result, ^MPFixableResult {
|
||||
for (MPElementType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
|
||||
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
|
||||
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
|
||||
self.name, self.user.name, (long)self.type, self.class, (long)newType );
|
||||
self.type = newType;
|
||||
return MPFixableResultProblemsFixed;
|
||||
}
|
||||
|
||||
err( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Couldn't find a type to fix problem with.",
|
||||
self.name, self.user.name, (long)self.type, self.class );
|
||||
return MPFixableResultProblemsNotFixed;
|
||||
} );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -12,6 +12,6 @@
|
||||
|
||||
@interface MPElementStoredEntity : MPElementEntity
|
||||
|
||||
@property(nonatomic, retain) id contentObject;
|
||||
@property(nonatomic, retain) NSData *contentObject;
|
||||
|
||||
@end
|
||||
|
@@ -7,9 +7,31 @@
|
||||
//
|
||||
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@implementation MPElementStoredEntity
|
||||
|
||||
@dynamic contentObject;
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
|
||||
|
||||
if (self.contentObject && ![self.contentObject isKindOfClass:[NSData class]])
|
||||
result = MPApplyFix( result, ^MPFixableResult {
|
||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||
if (key && [[MPAppDelegate_Shared get] activeUserInContext:context] == self.user) {
|
||||
wrn( @"Content object not encrypted for: %@ of %@. Will re-encrypt.", self.name, self.user.name );
|
||||
[self.algorithm saveContent:[self.contentObject description] toElement:self usingKey:key];
|
||||
return MPFixableResultProblemsFixed;
|
||||
}
|
||||
|
||||
err( @"Content object not encrypted for: %@ of %@. Couldn't fix, please sign in.", self.name, self.user.name );
|
||||
return MPFixableResultProblemsNotFixed;
|
||||
} );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -35,6 +35,8 @@
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
||||
- (NSString *)resolveContentUsingKey:(MPKey *)key;
|
||||
- (void)resolveContentUsingKey:(MPKey *)key result:(void (^)(NSString *))result;
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -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]);
|
||||
}
|
||||
@@ -36,26 +38,11 @@
|
||||
|
||||
- (MPElementType)type {
|
||||
|
||||
// Some people got elements with type == 0.
|
||||
MPElementType type = (MPElementType)[self.type_ unsignedIntegerValue];
|
||||
if (!type || type == (MPElementType)NSNotFound)
|
||||
type = [self.user defaultType];
|
||||
if (!type || type == (MPElementType)NSNotFound)
|
||||
type = MPElementTypeGeneratedLong;
|
||||
|
||||
return type;
|
||||
return (MPElementType)[self.type_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setType:(MPElementType)aType {
|
||||
|
||||
// Make sure we don't poison our model data with invalid values.
|
||||
if (!aType || aType == (MPElementType)NSNotFound)
|
||||
aType = [self.user defaultType];
|
||||
if (!aType || aType == (MPElementType)NSNotFound)
|
||||
aType = MPElementTypeGeneratedLong;
|
||||
if (![self isKindOfClass:[self.algorithm classOfType:aType]])
|
||||
Throw(@"This object's class does not support the type: %lu", (long)aType);
|
||||
|
||||
self.type_ = @(aType);
|
||||
}
|
||||
|
||||
@@ -122,12 +109,12 @@
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return PearlString( @"%@:%@", [self class], [self name] );
|
||||
return strf( @"%@:%@", [self class], [self name] );
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
return PearlString( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
|
||||
return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
|
||||
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, (long)self.version,
|
||||
self.loginName, self.requiresExplicitMigration );
|
||||
}
|
||||
@@ -145,6 +132,16 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)resolveContentUsingKey:(MPKey *)key {
|
||||
|
||||
return [self.algorithm resolveContentForElement:self usingKey:key];
|
||||
}
|
||||
|
||||
- (void)resolveContentUsingKey:(MPKey *)key result:(void (^)(NSString *))result {
|
||||
|
||||
[self.algorithm resolveContentForElement:self usingKey:key result:result];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPElementGeneratedEntity(MP)
|
||||
@@ -189,7 +186,7 @@
|
||||
|
||||
- (MPElementType)defaultType {
|
||||
|
||||
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
|
||||
return IfElse((MPElementType)[self.defaultType_ unsignedIntegerValue], MPElementTypeGeneratedLong);
|
||||
}
|
||||
|
||||
- (void)setDefaultType:(MPElementType)aDefaultType {
|
||||
|
33
MasterPassword/ObjC/MPFixable.h
Normal file
33
MasterPassword/ObjC/MPFixable.h
Normal file
@@ -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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPFixable.h
|
||||
// MPFixable
|
||||
//
|
||||
// Created by lhunath on 2014-04-26.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM( NSUInteger, MPFixableResult ) {
|
||||
MPFixableResultNoProblems,
|
||||
MPFixableResultProblemsFixed,
|
||||
MPFixableResultProblemsNotFixed,
|
||||
};
|
||||
|
||||
MPFixableResult MPApplyFix(MPFixableResult previousResult, MPFixableResult(^fixBlock)(void));
|
||||
|
||||
@protocol MPFixable<NSObject>
|
||||
|
||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context;
|
||||
|
||||
@end
|
40
MasterPassword/ObjC/MPFixable.m
Normal file
40
MasterPassword/ObjC/MPFixable.m
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPFixable.m
|
||||
// MPFixable
|
||||
//
|
||||
// Created by lhunath on 2014-04-26.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFixable.h"
|
||||
|
||||
MPFixableResult MPApplyFix(MPFixableResult previousResult, MPFixableResult(^fixBlock)(void)) {
|
||||
|
||||
MPFixableResult additionalResult = fixBlock();
|
||||
switch (previousResult) {
|
||||
case MPFixableResultNoProblems:
|
||||
return additionalResult;
|
||||
case MPFixableResultProblemsFixed:
|
||||
switch (additionalResult) {
|
||||
case MPFixableResultNoProblems:
|
||||
case MPFixableResultProblemsFixed:
|
||||
return previousResult;
|
||||
case MPFixableResultProblemsNotFixed:
|
||||
return additionalResult;
|
||||
}
|
||||
case MPFixableResultProblemsNotFixed:
|
||||
return additionalResult;
|
||||
}
|
||||
|
||||
Throw( @"Unexpected previous=%ld or additional=%ld result.", (long)previousResult, (long)additionalResult );
|
||||
}
|
@@ -77,8 +77,10 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
|
||||
#define MPElementUpdatedNotification @"MPElementUpdatedNotification"
|
||||
#define MPCheckConfigNotification @"MPCheckConfigNotification"
|
||||
#define MPSitesImportedNotification @"MPSitesImportedNotification"
|
||||
#define MPFoundInconsistenciesNotification @"MPFoundInconsistenciesNotification"
|
||||
|
||||
#define MPSitesImportedNotificationUserKey @"MPSitesImportedNotificationUserKey"
|
||||
#define MPInconsistenciesFixResultUserKey @"MPInconsistenciesFixResultUserKey"
|
||||
|
||||
static void MPCheckpoint(NSString *checkpoint, NSDictionary *attributes) {
|
||||
|
||||
|
@@ -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];
|
||||
}];
|
||||
|
@@ -243,8 +243,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey ) userInfo:nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
|
||||
}
|
||||
|
||||
#pragma mark - NSApplicationDelegate
|
||||
|
@@ -182,7 +182,7 @@
|
||||
// "Create" button.
|
||||
[[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element) {
|
||||
if (element)
|
||||
[self updateElements];
|
||||
PearlMainQueue( ^{ [self updateElements]; } );
|
||||
}];
|
||||
break;
|
||||
}
|
||||
@@ -453,7 +453,7 @@
|
||||
- (void)setElementSelectionIndexes:(NSIndexSet *)elementSelectionIndexes {
|
||||
|
||||
// First reset bounds.
|
||||
PearlMainThread(^{
|
||||
PearlMainQueue(^{
|
||||
NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex;
|
||||
if (selectedIndex != NSNotFound && selectedIndex < self.elements.count)
|
||||
[[self selectedView].animator setBoundsOrigin:NSZeroPoint];
|
||||
|
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
DA3B84581915577F00246EEA /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3B84571915577F00246EEA /* Crashlytics.framework */; };
|
||||
DAD9B5CF176299B9001835F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAD9B5CD176299B9001835F9 /* InfoPlist.strings */; };
|
||||
DAD9B5D1176299B9001835F9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD9B5D0176299B9001835F9 /* main.m */; };
|
||||
DAD9B5D8176299B9001835F9 /* MPLoginAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD9B5D7176299B9001835F9 /* MPLoginAppDelegate.m */; };
|
||||
@@ -14,6 +15,7 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
DA3B84571915577F00246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = ../../../../External/Mac/Crashlytics.framework; sourceTree = "<group>"; };
|
||||
DAD9B5C1176299B9001835F9 /* MasterPassword-Mac-LoginHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MasterPassword-Mac-LoginHelper.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DAD9B5CC176299B9001835F9 /* MasterPassword-Mac-LoginHelper-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MasterPassword-Mac-LoginHelper-Info.plist"; sourceTree = "<group>"; };
|
||||
DAD9B5CE176299B9001835F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -30,6 +32,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DAD9B5EC1762AAA6001835F9 /* AppKit.framework in Frameworks */,
|
||||
DA3B84581915577F00246EEA /* Crashlytics.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -56,6 +59,7 @@
|
||||
DAD9B5C3176299B9001835F9 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3B84571915577F00246EEA /* Crashlytics.framework */,
|
||||
DAD9B5EB1762AAA6001835F9 /* AppKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -164,7 +168,69 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
DAD9B5DC176299B9001835F9 /* Debug */ = {
|
||||
DA3B845A1916AC5100246EEA /* AppStore-Mac */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
|
||||
GCC_WARN_SIGN_COMPARE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = "AppStore-Mac";
|
||||
};
|
||||
DA3B845B1916AC5100246EEA /* AppStore-Mac */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper.entitlements";
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application: Maarten Billemont (HL3Q45LX9N)";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
);
|
||||
INFOPLIST_FILE = "$(SRCROOT)/MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper-Info.plist";
|
||||
};
|
||||
name = "AppStore-Mac";
|
||||
};
|
||||
DAD9B5DC176299B9001835F9 /* Debug-Mac */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
@@ -185,7 +251,6 @@
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application: Maarten Billemont";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
@@ -215,13 +280,12 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
name = "Debug-Mac";
|
||||
};
|
||||
DAD9B5DD176299B9001835F9 /* Release */ = {
|
||||
DAD9B5DD176299B9001835F9 /* AdHoc-Mac */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
@@ -242,7 +306,6 @@
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application: Maarten Billemont";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
@@ -265,31 +328,38 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
name = "AdHoc-Mac";
|
||||
};
|
||||
DAD9B5DF176299B9001835F9 /* Debug */ = {
|
||||
DAD9B5DF176299B9001835F9 /* Debug-Mac */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application: Maarten Billemont";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
);
|
||||
INFOPLIST_FILE = "$(SRCROOT)/MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper-Info.plist";
|
||||
PROVISIONING_PROFILE = "";
|
||||
};
|
||||
name = Debug;
|
||||
name = "Debug-Mac";
|
||||
};
|
||||
DAD9B5E0176299B9001835F9 /* Release */ = {
|
||||
DAD9B5E0176299B9001835F9 /* AdHoc-Mac */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
);
|
||||
INFOPLIST_FILE = "$(SRCROOT)/MasterPassword-Mac-LoginHelper/MasterPassword-Mac-LoginHelper-Info.plist";
|
||||
};
|
||||
name = Release;
|
||||
name = "AdHoc-Mac";
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
@@ -297,20 +367,22 @@
|
||||
DAD9B5BC176299B9001835F9 /* Build configuration list for PBXProject "MasterPassword-Mac-LoginHelper" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DAD9B5DC176299B9001835F9 /* Debug */,
|
||||
DAD9B5DD176299B9001835F9 /* Release */,
|
||||
DAD9B5DC176299B9001835F9 /* Debug-Mac */,
|
||||
DAD9B5DD176299B9001835F9 /* AdHoc-Mac */,
|
||||
DA3B845A1916AC5100246EEA /* AppStore-Mac */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = "AdHoc-Mac";
|
||||
};
|
||||
DAD9B5DE176299B9001835F9 /* Build configuration list for PBXNativeTarget "MasterPassword-Mac-LoginHelper" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DAD9B5DF176299B9001835F9 /* Debug */,
|
||||
DAD9B5E0176299B9001835F9 /* Release */,
|
||||
DAD9B5DF176299B9001835F9 /* Debug-Mac */,
|
||||
DAD9B5E0176299B9001835F9 /* AdHoc-Mac */,
|
||||
DA3B845B1916AC5100246EEA /* AppStore-Mac */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
defaultConfigurationName = "AdHoc-Mac";
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
|
@@ -21,6 +21,12 @@
|
||||
DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; };
|
||||
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
|
||||
DA1E4D50176E0E280065E0EF /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1E4D4F176E0E280065E0EF /* Media.xcassets */; };
|
||||
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; };
|
||||
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */; };
|
||||
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */; };
|
||||
DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */; };
|
||||
DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */; };
|
||||
DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */; };
|
||||
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
|
||||
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
|
||||
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
|
||||
@@ -29,6 +35,10 @@
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
|
||||
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; };
|
||||
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; };
|
||||
DA3B844F190FC60900246EEA /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3B844A190FC5A900246EEA /* Crashlytics.framework */; };
|
||||
DA3B8452190FC86F00246EEA /* NSManagedObject+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3B8450190FC86F00246EEA /* NSManagedObject+Pearl.m */; };
|
||||
DA3B8453190FC86F00246EEA /* NSManagedObject+Pearl.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3B8451190FC86F00246EEA /* NSManagedObject+Pearl.h */; };
|
||||
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3B8454190FC89700246EEA /* MPFixable.m */; };
|
||||
DA3EF17B15A47744003ABF4E /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */; };
|
||||
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
|
||||
@@ -109,9 +119,6 @@
|
||||
DACA27361705DF81002C6C22 /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24561705DF7D002C6C22 /* avatar-0@2x.png */; };
|
||||
DACA27371705DF81002C6C22 /* avatar-10@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24571705DF7D002C6C22 /* avatar-10@2x.png */; };
|
||||
DACA27381705DF81002C6C22 /* menu-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24581705DF7D002C6C22 /* menu-icon.png */; };
|
||||
DACA29641705DF81002C6C22 /* Exo-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268B1705DF81002C6C22 /* Exo-ExtraBold.otf */; };
|
||||
DACA29651705DF81002C6C22 /* Exo-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268C1705DF81002C6C22 /* Exo-Regular.otf */; };
|
||||
DACA29661705DF81002C6C22 /* Exo-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268D1705DF81002C6C22 /* Exo-Bold.otf */; };
|
||||
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */; };
|
||||
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */; };
|
||||
DACA296D1705DF81002C6C22 /* Localytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DACA26961705DF81002C6C22 /* Localytics.plist */; };
|
||||
@@ -204,6 +211,10 @@
|
||||
DAEB942818AB0FFD000490CC /* sysendian.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D718AB0FFD000490CC /* sysendian.h */; };
|
||||
DAEB942918AB0FFD000490CC /* warn.h in Headers */ = {isa = PBXBuildFile; fileRef = DAEB93D818AB0FFD000490CC /* warn.h */; };
|
||||
DAEB942E18B47FB3000490CC /* MPInitialWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */; };
|
||||
DAF4EF56190A828100023C90 /* Exo2.0-Thin.otf in Resources */ = {isa = PBXBuildFile; fileRef = DAF4EF52190A828100023C90 /* Exo2.0-Thin.otf */; };
|
||||
DAF4EF57190A828100023C90 /* Exo2.0-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DAF4EF53190A828100023C90 /* Exo2.0-Regular.otf */; };
|
||||
DAF4EF58190A828100023C90 /* Exo2.0-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DAF4EF54190A828100023C90 /* Exo2.0-ExtraBold.otf */; };
|
||||
DAF4EF59190A828100023C90 /* Exo2.0-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DAF4EF55190A828100023C90 /* Exo2.0-Bold.otf */; };
|
||||
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */; };
|
||||
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */; };
|
||||
DAFE4A1515039824003ABA7C /* NSString+PearlNSArrayFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */; };
|
||||
@@ -308,6 +319,12 @@
|
||||
DA16B340170661DB000A0EAB /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
|
||||
DA16B343170661EE000A0EAB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
DA1E4D4F176E0E280065E0EF /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = MasterPassword/Media.xcassets; sourceTree = "<group>"; };
|
||||
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
|
||||
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
|
||||
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = "<group>"; };
|
||||
DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Pearl.h"; sourceTree = "<group>"; };
|
||||
DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+PearlBlock.m"; sourceTree = "<group>"; };
|
||||
DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+PearlBlock.h"; sourceTree = "<group>"; };
|
||||
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
|
||||
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
|
||||
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
|
||||
@@ -316,6 +333,12 @@
|
||||
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
|
||||
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = "<group>"; };
|
||||
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
|
||||
DA3B8448190FC5A900246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
|
||||
DA3B844A190FC5A900246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
|
||||
DA3B8450190FC86F00246EEA /* NSManagedObject+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+Pearl.m"; sourceTree = "<group>"; };
|
||||
DA3B8451190FC86F00246EEA /* NSManagedObject+Pearl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObject+Pearl.h"; sourceTree = "<group>"; };
|
||||
DA3B8454190FC89700246EEA /* MPFixable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFixable.m; sourceTree = "<group>"; };
|
||||
DA3B8455190FC89700246EEA /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = "<group>"; };
|
||||
DA3EF17915A47744003ABF4E /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA3EF17A15A47744003ABF4E /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
DA4425CB1557BED40052177D /* libUbiquityStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUbiquityStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -381,7 +404,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 = "<group>"; };
|
||||
DACA22181705DE28002C6C22 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
|
||||
DACA22B71705DE7D002C6C22 /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = "<group>"; };
|
||||
DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+UbiquityStoreManager.h"; sourceTree = "<group>"; };
|
||||
DACA22B91705DE7D002C6C22 /* NSError+UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+UbiquityStoreManager.m"; sourceTree = "<group>"; };
|
||||
@@ -428,9 +450,6 @@
|
||||
DACA24561705DF7D002C6C22 /* avatar-0@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-0@2x.png"; sourceTree = "<group>"; };
|
||||
DACA24571705DF7D002C6C22 /* avatar-10@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-10@2x.png"; sourceTree = "<group>"; };
|
||||
DACA24581705DF7D002C6C22 /* menu-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu-icon.png"; sourceTree = "<group>"; };
|
||||
DACA268B1705DF81002C6C22 /* Exo-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-ExtraBold.otf"; sourceTree = "<group>"; };
|
||||
DACA268C1705DF81002C6C22 /* Exo-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-Regular.otf"; sourceTree = "<group>"; };
|
||||
DACA268D1705DF81002C6C22 /* Exo-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo-Bold.otf"; sourceTree = "<group>"; };
|
||||
DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-ExtraLight.otf"; sourceTree = "<group>"; };
|
||||
DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-Black.otf"; sourceTree = "<group>"; };
|
||||
DACA26961705DF81002C6C22 /* Localytics.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Localytics.plist; sourceTree = "<group>"; };
|
||||
@@ -524,6 +543,10 @@
|
||||
DAEB93D718AB0FFD000490CC /* sysendian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sysendian.h; sourceTree = "<group>"; };
|
||||
DAEB93D818AB0FFD000490CC /* warn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = warn.h; sourceTree = "<group>"; };
|
||||
DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
DAF4EF52190A828100023C90 /* Exo2.0-Thin.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Thin.otf"; sourceTree = "<group>"; };
|
||||
DAF4EF53190A828100023C90 /* Exo2.0-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Regular.otf"; sourceTree = "<group>"; };
|
||||
DAF4EF54190A828100023C90 /* Exo2.0-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-ExtraBold.otf"; sourceTree = "<group>"; };
|
||||
DAF4EF55190A828100023C90 /* Exo2.0-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Bold.otf"; sourceTree = "<group>"; };
|
||||
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; };
|
||||
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; };
|
||||
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlNSArrayFormat.h"; sourceTree = "<group>"; };
|
||||
@@ -598,6 +621,7 @@
|
||||
DA16B341170661DB000A0EAB /* Carbon.framework in Frameworks */,
|
||||
DA16B342170661E0000A0EAB /* Security.framework in Frameworks */,
|
||||
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */,
|
||||
DA3B844F190FC60900246EEA /* Crashlytics.framework in Frameworks */,
|
||||
DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -631,6 +655,22 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
DA3B8447190FC5A900246EEA /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3B8448190FC5A900246EEA /* Crashlytics.framework */,
|
||||
);
|
||||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA3B8449190FC5A900246EEA /* Mac */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3B844A190FC5A900246EEA /* Crashlytics.framework */,
|
||||
);
|
||||
path = Mac;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA5BFA39147E415C00F98B1E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -689,6 +729,8 @@
|
||||
DA5E5C961724A667003798D8 /* ObjC */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3B8454190FC89700246EEA /* MPFixable.m */,
|
||||
DA3B8455190FC89700246EEA /* MPFixable.h */,
|
||||
DA5E5CB21724A667003798D8 /* Mac */,
|
||||
DA5E5C971724A667003798D8 /* MPAlgorithm.h */,
|
||||
DA5E5C981724A667003798D8 /* MPAlgorithm.m */,
|
||||
@@ -773,9 +815,10 @@
|
||||
DACA22121705DDC5002C6C22 /* External */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3B8447190FC5A900246EEA /* iOS */,
|
||||
DA3B8449190FC5A900246EEA /* Mac */,
|
||||
DABC6C0E175D8CE1000C15D4 /* RHStatusItemView */,
|
||||
DACA29751705E2BD002C6C22 /* jrswizzle */,
|
||||
DACA22181705DE28002C6C22 /* Crashlytics.framework */,
|
||||
DAC77CAF148291A600BCF976 /* Pearl */,
|
||||
DACA22B61705DE7D002C6C22 /* UbiquityStoreManager */,
|
||||
);
|
||||
@@ -879,9 +922,10 @@
|
||||
DACA268A1705DF81002C6C22 /* Fonts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DACA268B1705DF81002C6C22 /* Exo-ExtraBold.otf */,
|
||||
DACA268C1705DF81002C6C22 /* Exo-Regular.otf */,
|
||||
DACA268D1705DF81002C6C22 /* Exo-Bold.otf */,
|
||||
DAF4EF52190A828100023C90 /* Exo2.0-Thin.otf */,
|
||||
DAF4EF53190A828100023C90 /* Exo2.0-Regular.otf */,
|
||||
DAF4EF54190A828100023C90 /* Exo2.0-ExtraBold.otf */,
|
||||
DAF4EF55190A828100023C90 /* Exo2.0-Bold.otf */,
|
||||
DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */,
|
||||
DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */,
|
||||
);
|
||||
@@ -1038,6 +1082,14 @@
|
||||
DAFE45D715039823003ABA7C /* Pearl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA3B8450190FC86F00246EEA /* NSManagedObject+Pearl.m */,
|
||||
DA3B8451190FC86F00246EEA /* NSManagedObject+Pearl.h */,
|
||||
DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */,
|
||||
DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */,
|
||||
DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */,
|
||||
DA2CA4EA18D323D3007798F8 /* NSArray+Pearl.h */,
|
||||
DA2CA4EB18D323D3007798F8 /* NSTimer+PearlBlock.m */,
|
||||
DA2CA4EC18D323D3007798F8 /* NSTimer+PearlBlock.h */,
|
||||
DA3509FC15F101A500C14A8E /* PearlQueue.h */,
|
||||
DA3509FD15F101A500C14A8E /* PearlQueue.m */,
|
||||
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */,
|
||||
@@ -1145,6 +1197,7 @@
|
||||
DAEB93FD18AB0FFD000490CC /* mdc2.h in Headers */,
|
||||
DAEB942718AB0FFD000490CC /* sha256.h in Headers */,
|
||||
DAEB940818AB0FFD000490CC /* pkcs7.h in Headers */,
|
||||
DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */,
|
||||
DAEB93DF18AB0FFD000490CC /* bn.h in Headers */,
|
||||
DAEB940718AB0FFD000490CC /* pkcs12.h in Headers */,
|
||||
DAEB941A18AB0FFD000490CC /* txt_db.h in Headers */,
|
||||
@@ -1154,6 +1207,7 @@
|
||||
DAEB941018AB0FFD000490CC /* seed.h in Headers */,
|
||||
DAEB942618AB0FFD000490CC /* scryptenc_cpuperf.h in Headers */,
|
||||
DAFE4A1715039824003ABA7C /* NSString+PearlSEL.h in Headers */,
|
||||
DA3B8453190FC86F00246EEA /* NSManagedObject+Pearl.h in Headers */,
|
||||
DAEB93F518AB0FFD000490CC /* evp.h in Headers */,
|
||||
DAEB941918AB0FFD000490CC /* ts.h in Headers */,
|
||||
DAEB93F818AB0FFD000490CC /* krb5_asn.h in Headers */,
|
||||
@@ -1172,6 +1226,7 @@
|
||||
DAFE4A2415039824003ABA7C /* PearlInfoPlist.h in Headers */,
|
||||
DAEB940618AB0FFD000490CC /* pem2.h in Headers */,
|
||||
DAFE4A2615039824003ABA7C /* PearlLogger.h in Headers */,
|
||||
DA2CA4F018D323D3007798F8 /* NSArray+Pearl.h in Headers */,
|
||||
DAEB93FC18AB0FFD000490CC /* md5.h in Headers */,
|
||||
DAFE4A2815039824003ABA7C /* PearlMathUtils.h in Headers */,
|
||||
DAFE4A2A15039824003ABA7C /* PearlObjectUtils.h in Headers */,
|
||||
@@ -1242,6 +1297,7 @@
|
||||
DAEB93DB18AB0FFD000490CC /* asn1_mac.h in Headers */,
|
||||
DAEB940518AB0FFD000490CC /* pem.h in Headers */,
|
||||
DAEB942818AB0FFD000490CC /* sysendian.h in Headers */,
|
||||
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */,
|
||||
DAEB93FF18AB0FFD000490CC /* obj_mac.h in Headers */,
|
||||
DAEB93E718AB0FFD000490CC /* crypto.h in Headers */,
|
||||
DAEB941318AB0FFD000490CC /* ssl2.h in Headers */,
|
||||
@@ -1295,9 +1351,9 @@
|
||||
DA5BFA40147E415C00F98B1E /* Sources */,
|
||||
DA5BFA41147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA42147E415C00F98B1E /* Resources */,
|
||||
DAD9B5EE1762CA3A001835F9 /* Copy LoginHelper */,
|
||||
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */,
|
||||
DAD3125D155288AA00A3F9ED /* Run Script: Crashlytics */,
|
||||
DAD9B5EE1762CA3A001835F9 /* Copy LoginHelper */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1434,9 +1490,11 @@
|
||||
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */,
|
||||
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */,
|
||||
DACA27141705DF81002C6C22 /* avatar-7.png in Resources */,
|
||||
DAF4EF57190A828100023C90 /* Exo2.0-Regular.otf in Resources */,
|
||||
DACA27151705DF81002C6C22 /* avatar-0.png in Resources */,
|
||||
DACA27161705DF81002C6C22 /* avatar-12.png in Resources */,
|
||||
DACA27171705DF81002C6C22 /* avatar-15.png in Resources */,
|
||||
DAF4EF59190A828100023C90 /* Exo2.0-Bold.otf in Resources */,
|
||||
DACA27181705DF81002C6C22 /* avatar-9.png in Resources */,
|
||||
DACA27191705DF81002C6C22 /* avatar-1@2x.png in Resources */,
|
||||
DACA271A1705DF81002C6C22 /* avatar-11@2x.png in Resources */,
|
||||
@@ -1465,6 +1523,8 @@
|
||||
DACA272F1705DF81002C6C22 /* avatar-3.png in Resources */,
|
||||
DACA27301705DF81002C6C22 /* avatar-18.png in Resources */,
|
||||
DACA27311705DF81002C6C22 /* avatar-4.png in Resources */,
|
||||
DAF4EF58190A828100023C90 /* Exo2.0-ExtraBold.otf in Resources */,
|
||||
DAF4EF56190A828100023C90 /* Exo2.0-Thin.otf in Resources */,
|
||||
DACA27321705DF81002C6C22 /* avatar-16.png in Resources */,
|
||||
DACA27331705DF81002C6C22 /* avatar-12@2x.png in Resources */,
|
||||
DACA27341705DF81002C6C22 /* avatar-2@2x.png in Resources */,
|
||||
@@ -1472,9 +1532,6 @@
|
||||
DACA27361705DF81002C6C22 /* avatar-0@2x.png in Resources */,
|
||||
DACA27371705DF81002C6C22 /* avatar-10@2x.png in Resources */,
|
||||
DACA27381705DF81002C6C22 /* menu-icon.png in Resources */,
|
||||
DACA29641705DF81002C6C22 /* Exo-ExtraBold.otf in Resources */,
|
||||
DACA29651705DF81002C6C22 /* Exo-Regular.otf in Resources */,
|
||||
DACA29661705DF81002C6C22 /* Exo-Bold.otf in Resources */,
|
||||
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */,
|
||||
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */,
|
||||
DACA296D1705DF81002C6C22 /* Localytics.plist in Resources */,
|
||||
@@ -1535,7 +1592,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/bash -e";
|
||||
shellScript = "../../../External/Crashlytics.framework/run \\\n \"$(/usr/libexec/PlistBuddy -c \"Print :'API Key'\" ../../Resources/Crashlytics/Crashlytics.plist)\"";
|
||||
shellScript = "../../../External/Mac/Crashlytics.framework/run \\\n \"$(/usr/libexec/PlistBuddy -c \"Print :'API Key'\" ../../Resources/Crashlytics/Crashlytics.plist)\"";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
@@ -1571,6 +1628,7 @@
|
||||
DA5E5CFD1724A667003798D8 /* MPElementEntity.m in Sources */,
|
||||
DA5E5CFE1724A667003798D8 /* MPElementGeneratedEntity.m in Sources */,
|
||||
DA5E5CFF1724A667003798D8 /* MPElementStoredEntity.m in Sources */,
|
||||
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
|
||||
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
|
||||
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
|
||||
DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */,
|
||||
@@ -1607,6 +1665,7 @@
|
||||
DAFE4A1415039824003ABA7C /* NSObject+PearlExport.m in Sources */,
|
||||
DAFE4A1615039824003ABA7C /* NSString+PearlNSArrayFormat.m in Sources */,
|
||||
DAFE4A1815039824003ABA7C /* NSString+PearlSEL.m in Sources */,
|
||||
DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */,
|
||||
DAFE4A1B15039824003ABA7C /* PearlAbstractStrings.m in Sources */,
|
||||
DAFE4A1F15039824003ABA7C /* PearlCodeUtils.m in Sources */,
|
||||
DAFE4A2115039824003ABA7C /* PearlConfig.m in Sources */,
|
||||
@@ -1622,8 +1681,11 @@
|
||||
DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */,
|
||||
DAFE4A3B15039824003ABA7C /* PearlSCrypt.m in Sources */,
|
||||
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */,
|
||||
DA2CA4F118D323D3007798F8 /* NSTimer+PearlBlock.m in Sources */,
|
||||
DA3B8452190FC86F00246EEA /* NSManagedObject+Pearl.m in Sources */,
|
||||
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */,
|
||||
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */,
|
||||
DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */,
|
||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */,
|
||||
DAFE4A63150399FF003ABA86 /* NSObject+PearlKVO.m in Sources */,
|
||||
DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */,
|
||||
@@ -1814,7 +1876,6 @@
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -1867,7 +1928,6 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
@@ -1894,7 +1954,6 @@
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -1946,7 +2005,6 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
PROVISIONING_PROFILE = "";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -1960,7 +2018,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer: Maarten Billemont (DWGU95U4ZD)";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
);
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
SKIP_INSTALL = NO;
|
||||
@@ -1974,7 +2037,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application: Maarten Billemont (HL3Q45LX9N)";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
);
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
SKIP_INSTALL = NO;
|
||||
@@ -2003,7 +2071,6 @@
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -2055,7 +2122,6 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
PROVISIONING_PROFILE = "";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -2069,7 +2135,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements;
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application: Maarten Billemont (HL3Q45LX9N)";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Mac,
|
||||
);
|
||||
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
|
||||
INFOPLIST_FILE = "MasterPassword-Info.plist";
|
||||
SKIP_INSTALL = NO;
|
||||
|
23
MasterPassword/ObjC/iOS/MPAppSettingsViewController.h
Normal file
23
MasterPassword/ObjC/iOS/MPAppSettingsViewController.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAppSettingsViewController.h
|
||||
// MPAppSettingsViewController
|
||||
//
|
||||
// Created by lhunath on 2014-04-18.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "IASKAppSettingsViewController.h"
|
||||
|
||||
@interface MPAppSettingsViewController : IASKAppSettingsViewController
|
||||
@end
|
63
MasterPassword/ObjC/iOS/MPAppSettingsViewController.m
Normal file
63
MasterPassword/ObjC/iOS/MPAppSettingsViewController.m
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAppSettingsViewController.h
|
||||
// MPAppSettingsViewController
|
||||
//
|
||||
// Created by lhunath on 2014-04-18.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppSettingsViewController.h"
|
||||
#import "UIColor+Expanded.h"
|
||||
|
||||
@interface MPTableView:UITableView
|
||||
@end
|
||||
|
||||
@implementation MPTableView
|
||||
|
||||
- (void)layoutSubviews {
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)setContentInset:(UIEdgeInsets)contentInset {
|
||||
|
||||
[super setContentInset:contentInset];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPAppSettingsViewController {
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
|
||||
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
|
||||
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
|
||||
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
@end
|
47
MasterPassword/ObjC/iOS/MPAvatarCell.h
Normal file
47
MasterPassword/ObjC/iOS/MPAvatarCell.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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 <Foundation/Foundation.h>
|
||||
#import "MPEntities.h"
|
||||
@class MPAvatarCell;
|
||||
|
||||
/* Avatar with a "+" symbol. */
|
||||
extern const long MPAvatarAdd;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPAvatarMode) {
|
||||
MPAvatarModeLowered,
|
||||
MPAvatarModeRaisedButInactive,
|
||||
MPAvatarModeRaisedAndActive,
|
||||
MPAvatarModeRaisedAndHidden,
|
||||
MPAvatarModeRaisedAndMinimized,
|
||||
};
|
||||
|
||||
@interface MPAvatarCell : UICollectionViewCell
|
||||
@property (copy, nonatomic) NSString *name;
|
||||
@property (assign, nonatomic) long avatar;
|
||||
@property (assign, nonatomic) MPAvatarMode mode;
|
||||
@property (assign, nonatomic) CGFloat visibility;
|
||||
@property (assign, nonatomic) BOOL spinnerActive;
|
||||
@property (assign, nonatomic, readonly) BOOL newUser;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
|
||||
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated;
|
||||
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated;
|
||||
|
||||
@end
|
287
MasterPassword/ObjC/iOS/MPAvatarCell.m
Normal file
287
MasterPassword/ObjC/iOS/MPAvatarCell.m
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAvatarCell.h
|
||||
// MPAvatarCell
|
||||
//
|
||||
// Created by lhunath on 2014-03-11.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAvatarCell.h"
|
||||
#import "MPPasswordLargeCell.h"
|
||||
|
||||
const long MPAvatarAdd = 10000;
|
||||
|
||||
@interface MPAvatarCell()
|
||||
|
||||
@property(strong, nonatomic) IBOutlet UIImageView *avatarImageView;
|
||||
@property(strong, nonatomic) IBOutlet UILabel *nameLabel;
|
||||
@property(strong, nonatomic) IBOutlet UIView *nameContainer;
|
||||
@property(strong, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *nameToCenterConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarSizeConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarToTopConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarRaisedConstraint;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPAvatarCell {
|
||||
CAAnimationGroup *_targetedShadowAnimation;
|
||||
}
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
|
||||
return NSStringFromClass( self );
|
||||
}
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
- (void)awakeFromNib {
|
||||
|
||||
[super awakeFromNib];
|
||||
|
||||
self.alpha = 0;
|
||||
|
||||
self.nameContainer.layer.cornerRadius = 5;
|
||||
|
||||
self.avatarImageView.hidden = NO;
|
||||
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
|
||||
self.avatarImageView.layer.masksToBounds = NO;
|
||||
self.avatarImageView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||
[_self updateAnimated:YES];
|
||||
}];
|
||||
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||
[_self updateAnimated:YES];
|
||||
}];
|
||||
|
||||
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
toShadowOpacityAnimation.toValue = @0.2f;
|
||||
toShadowOpacityAnimation.duration = 0.5f;
|
||||
|
||||
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
pulseShadowOpacityAnimation.fromValue = @0.2f;
|
||||
pulseShadowOpacityAnimation.toValue = @0.6f;
|
||||
pulseShadowOpacityAnimation.beginTime = 0.5f;
|
||||
pulseShadowOpacityAnimation.duration = 2.0f;
|
||||
pulseShadowOpacityAnimation.autoreverses = YES;
|
||||
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
|
||||
|
||||
_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 {
|
||||
|
||||
[self removeKeyPathObservers];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setAvatar:(long)avatar {
|
||||
|
||||
_avatar = avatar == MPAvatarAdd? MPAvatarAdd: (avatar + MPAvatarCount) % MPAvatarCount;
|
||||
|
||||
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 )];
|
||||
}
|
||||
|
||||
- (NSString *)name {
|
||||
|
||||
return self.nameLabel.text;
|
||||
}
|
||||
|
||||
- (void)setName:(NSString *)name {
|
||||
|
||||
self.nameLabel.text = name;
|
||||
}
|
||||
|
||||
- (void)setVisibility:(CGFloat)visibility {
|
||||
|
||||
[self setVisibility:visibility animated:YES];
|
||||
}
|
||||
|
||||
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated {
|
||||
|
||||
_visibility = visibility;
|
||||
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted {
|
||||
|
||||
super.highlighted = highlighted;
|
||||
|
||||
[UIView animateWithDuration:0.1f animations:^{
|
||||
self.avatarImageView.transform = highlighted? CGAffineTransformMakeScale( 1.1f, 1.1f ): CGAffineTransformIdentity;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setMode:(MPAvatarMode)mode {
|
||||
|
||||
[self setMode:mode animated:YES];
|
||||
}
|
||||
|
||||
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated {
|
||||
|
||||
_mode = mode;
|
||||
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)setSpinnerActive:(BOOL)spinnerActive {
|
||||
|
||||
[self setSpinnerActive:spinnerActive animated:YES];
|
||||
}
|
||||
|
||||
- (void)setSpinnerActive:(BOOL)spinnerActive animated:(BOOL)animated {
|
||||
|
||||
if (_spinnerActive == spinnerActive)
|
||||
return;
|
||||
_spinnerActive = spinnerActive;
|
||||
|
||||
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
|
||||
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
|
||||
rotate.duration = 5.0;
|
||||
|
||||
if (spinnerActive) {
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
rotate.fromValue = @0.0;
|
||||
rotate.repeatCount = MAXFLOAT;
|
||||
}
|
||||
else {
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
rotate.repeatCount = 1;
|
||||
}
|
||||
|
||||
[self.spinner.layer removeAnimationForKey:@"rotation"];
|
||||
[self.spinner.layer addAnimation:rotate forKey:@"rotation"];
|
||||
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated {
|
||||
|
||||
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
|
||||
self.avatarImageView.transform = CGAffineTransformIdentity;
|
||||
}];
|
||||
[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;
|
||||
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameContainer.alpha = self.visibility;
|
||||
self.nameContainer.backgroundColor = [UIColor clearColor];
|
||||
self.avatarImageView.alpha = self.visibility / 0.7f + 0.3f;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedButInactive: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameContainer.alpha = self.visibility;
|
||||
self.nameContainer.backgroundColor = [UIColor clearColor];
|
||||
self.avatarImageView.alpha = 0;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedAndActive: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameContainer.alpha = self.visibility;
|
||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||
self.avatarImageView.alpha = 1;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedAndHidden: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameContainer.alpha = 0;
|
||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||
self.avatarImageView.alpha = 0;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedAndMinimized: {
|
||||
self.avatarSizeConstraint.constant = 36;
|
||||
self.avatarRaisedConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.avatarToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameToCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameContainer.alpha = 0;
|
||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||
self.avatarImageView.alpha = 1;
|
||||
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;
|
||||
else
|
||||
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
|
23
MasterPassword/ObjC/iOS/MPCell.h
Normal file
23
MasterPassword/ObjC/iOS/MPCell.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCell.h
|
||||
// MPCell
|
||||
//
|
||||
// Created by lhunath on 2014-03-27.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MPCell : UICollectionViewCell
|
||||
|
||||
@end
|
24
MasterPassword/ObjC/iOS/MPCell.m
Normal file
24
MasterPassword/ObjC/iOS/MPCell.m
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCell.h
|
||||
// MPCell
|
||||
//
|
||||
// Created by lhunath on 2014-03-27.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPCell.h"
|
||||
|
||||
@implementation MPCell {
|
||||
}
|
||||
|
||||
@end
|
48
MasterPassword/ObjC/iOS/MPCoachmarkViewController.h
Normal file
48
MasterPassword/ObjC/iOS/MPCoachmarkViewController.h
Normal file
@@ -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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCoachmarkViewController.h
|
||||
// MPCoachmarkViewController
|
||||
//
|
||||
// Created by lhunath on 2014-04-22.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MPCoachmark : NSObject
|
||||
|
||||
@property(nonatomic, strong) Class coachedClass;
|
||||
@property(nonatomic) NSInteger coachedVersion;
|
||||
@property(nonatomic) BOOL coached;
|
||||
|
||||
+ (instancetype)coachmarkForClass:(Class)class version:(NSInteger)version;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPCoachmarkViewController : UIViewController
|
||||
|
||||
@property(nonatomic, strong) MPCoachmark *coachmark;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view0;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view1;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view2;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view3;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view4;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view5;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view6;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view7;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view8;
|
||||
@property(nonatomic, strong) IBOutlet UIView *view9;
|
||||
@property(nonatomic, strong) IBOutlet UIProgressView *viewProgress;
|
||||
|
||||
- (IBAction)close:(id)sender;
|
||||
|
||||
@end
|
104
MasterPassword/ObjC/iOS/MPCoachmarkViewController.m
Normal file
104
MasterPassword/ObjC/iOS/MPCoachmarkViewController.m
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCoachmarkViewController.h
|
||||
// MPCoachmarkViewController
|
||||
//
|
||||
// Created by lhunath on 2014-04-22.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPCoachmarkViewController.h"
|
||||
|
||||
@implementation MPCoachmarkViewController {
|
||||
NSArray *_views;
|
||||
NSUInteger _nextView;
|
||||
__weak NSTimer *_viewTimer;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
_views = [NSArray arrayWithObjects:
|
||||
self.view0, self.view1, self.view2, self.view3, self.view4, self.view5, self.view6, self.view7, self.view8, self.view9, nil];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.viewProgress.hidden = NO;
|
||||
self.viewProgress.progress = 0;
|
||||
[_views makeObjectsPerformSelector:@selector( setAlpha: ) withObject:@0];
|
||||
_nextView = 0;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[_views[_nextView++] setAlpha:1];
|
||||
}];
|
||||
|
||||
_viewTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 block:^(NSTimer *timer) {
|
||||
self.viewProgress.progress += 1.0f / 50;
|
||||
|
||||
if (self.viewProgress.progress == 1)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.viewProgress.progress = 0;
|
||||
[_views[_nextView++] setAlpha:1];
|
||||
|
||||
if (_nextView >= [_views count]) {
|
||||
[_viewTimer invalidate];
|
||||
self.viewProgress.hidden = YES;
|
||||
}
|
||||
}];
|
||||
} repeats:YES];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (IBAction)close:(id)sender {
|
||||
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
self.coachmark.coached = YES;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPCoachmark
|
||||
|
||||
+ (instancetype)coachmarkForClass:(Class)coachedClass version:(NSInteger)coachedVersion {
|
||||
|
||||
MPCoachmark *coachmark = [self new];
|
||||
coachmark.coachedClass = coachedClass;
|
||||
coachmark.coachedVersion = coachedVersion;
|
||||
|
||||
return coachmark;
|
||||
}
|
||||
|
||||
- (BOOL)coached {
|
||||
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:strf( @"%@.%ld.coached", self.coachedClass, (long)self.coachedVersion )];
|
||||
}
|
||||
|
||||
- (void)setCoached:(BOOL)coached {
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setBool:coached forKey:strf( @"%@.%ld.coached", self.coachedClass, (long)self.coachedVersion )];
|
||||
}
|
||||
|
||||
@end
|
29
MasterPassword/ObjC/iOS/MPCombinedViewController.h
Normal file
29
MasterPassword/ObjC/iOS/MPCombinedViewController.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPCombinedMode) {
|
||||
MPCombinedModeUserSelection,
|
||||
MPCombinedModePasswordSelection,
|
||||
};
|
||||
|
||||
@interface MPCombinedViewController : UIViewController
|
||||
|
||||
@property(assign, nonatomic) MPCombinedMode mode;
|
||||
@property(strong, nonatomic) IBOutlet UIView *usersView;
|
||||
|
||||
@end
|
187
MasterPassword/ObjC/iOS/MPCombinedViewController.m
Normal file
187
MasterPassword/ObjC/iOS/MPCombinedViewController.m
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPCombinedViewController.h"
|
||||
#import "MPUsersViewController.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
#import "MPEmergencySegue.h"
|
||||
#import "MPEmergencyViewController.h"
|
||||
#import "MPPasswordsSegue.h"
|
||||
|
||||
@interface MPCombinedViewController()
|
||||
|
||||
@property(nonatomic, weak) MPUsersViewController *usersVC;
|
||||
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
|
||||
@end
|
||||
|
||||
@implementation MPCombinedViewController {
|
||||
NSArray *_notificationObservers;
|
||||
MPPasswordsViewController *_passwordsVC;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setMode:MPCombinedModeUserSelection animated:NO];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[[self navigationController] setNavigationBarHidden:YES animated:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[self registerObservers];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"users"])
|
||||
self.usersVC = segue.destinationViewController;
|
||||
if ([segue.identifier isEqualToString:@"passwords"]) {
|
||||
NSAssert([segue isKindOfClass:[MPPasswordsSegue class]], @"passwords segue should be MPPasswordsSegue: %@", segue);
|
||||
NSAssert([sender isKindOfClass:[NSDictionary class]], @"sender should be dictionary: %@", sender);
|
||||
NSAssert([[sender objectForKey:@"animated"] isKindOfClass:[NSNumber class]], @"sender should contain 'animated': %@", sender);
|
||||
[(MPPasswordsSegue *)segue setAnimated:[sender[@"animated"] boolValue]];
|
||||
UIViewController *destinationVC = segue.destinationViewController;
|
||||
_passwordsVC = [destinationVC isKindOfClass:[MPPasswordsViewController class]]? (MPPasswordsViewController *)destinationVC: nil;
|
||||
}
|
||||
if ([segue.identifier isEqualToString:@"emergency"])
|
||||
self.emergencyVC = segue.destinationViewController;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
|
||||
return self.mode == MPCombinedModeUserSelection;
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
|
||||
if (motion == UIEventSubtypeMotionShake && !self.emergencyVC)
|
||||
[self performSegueWithIdentifier:@"emergency" sender:self];
|
||||
}
|
||||
|
||||
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
|
||||
fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
|
||||
|
||||
if ([identifier isEqualToString:@"unwind-emergency"]) {
|
||||
MPEmergencySegue *segue = [[MPEmergencySegue alloc] initWithIdentifier:identifier
|
||||
source:fromViewController destination:toViewController];
|
||||
segue.unwind = YES;
|
||||
dbg_return(segue);
|
||||
}
|
||||
|
||||
dbg_return((id)nil);
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setMode:(MPCombinedMode)mode {
|
||||
|
||||
[self setMode:mode animated:YES];
|
||||
}
|
||||
|
||||
- (void)setMode:(MPCombinedMode)mode animated:(BOOL)animated {
|
||||
|
||||
if (_mode == mode && animated)
|
||||
return;
|
||||
_mode = mode;
|
||||
|
||||
[self setNeedsStatusBarAppearanceUpdate];
|
||||
[self becomeFirstResponder];
|
||||
[self.usersVC setNeedsStatusBarAppearanceUpdate];
|
||||
[self.usersVC.view setNeedsUpdateConstraints];
|
||||
[self.usersVC.view setNeedsLayout];
|
||||
|
||||
switch (self.mode) {
|
||||
case MPCombinedModeUserSelection: {
|
||||
self.usersView.userInteractionEnabled = YES;
|
||||
[self.usersVC setActive:YES animated:animated];
|
||||
if (_passwordsVC) {
|
||||
MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self];
|
||||
[self prepareForSegue:segue sender:@{ @"animated" : @(animated) }];
|
||||
[segue perform];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPCombinedModePasswordSelection: {
|
||||
self.usersView.userInteractionEnabled = NO;
|
||||
[self.usersVC setActive:NO animated:animated];
|
||||
[self performSegueWithIdentifier:@"passwords" sender:@{ @"animated" : @(animated) }];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
if ([_notificationObservers count])
|
||||
return;
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:MPSignedInNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self setMode:MPCombinedModePasswordSelection];
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:MPSignedOutNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self setMode:MPCombinedModeUserSelection animated:[note.userInfo[@"animated"] boolValue]];
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
|
||||
for (id observer in _notificationObservers)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
_notificationObservers = nil;
|
||||
}
|
||||
|
||||
@end
|
@@ -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];
|
||||
@@ -149,7 +150,7 @@
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[PearlEMail sendEMailTo:nil subject:@"[Master Password] Upgrade Changes" body:formattedChanges];
|
||||
[PearlEMail sendEMailTo:nil fromVC:self subject:@"[Master Password] Upgrade Changes" body:formattedChanges];
|
||||
} cancelTitle:@"Don't Email" otherTitles:@"Send Email", nil];
|
||||
}
|
||||
|
||||
|
@@ -260,12 +260,9 @@ forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete) {
|
||||
NSManagedObjectID *elementOID = [self elementForTableIndexPath:indexPath].objectID;
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
NSError *error = nil;
|
||||
MPElementEntity *element = (MPElementEntity *)[context existingObjectWithID:elementOID error:&error];
|
||||
if (!element) {
|
||||
err(@"Failed to retrieve element to delete: %@", error);
|
||||
MPElementEntity *element = [MPElementEntity existingObjectWithID:elementOID inContext:context];
|
||||
if (!element)
|
||||
return;
|
||||
}
|
||||
|
||||
inf(@"Deleting element: %@", element.name);
|
||||
[context deleteObject:element];
|
||||
|
@@ -105,7 +105,7 @@
|
||||
|
||||
UISearchBar *searchBar = self.searchDisplayController.searchBar;
|
||||
CGRect searchBarFrame = searchBar.frame;
|
||||
[searchBar.superview enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
[searchBar.superview enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
|
||||
if ([subview isKindOfClass:[UIControl class]] &&
|
||||
CGPointEqualToPoint(
|
||||
@@ -118,7 +118,7 @@
|
||||
|
||||
*stop = YES;
|
||||
}
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
}
|
||||
|
||||
- (BOOL)newSiteSectionNeeded {
|
||||
@@ -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];
|
||||
}
|
||||
|
24
MasterPassword/ObjC/iOS/MPEmergencySegue.h
Normal file
24
MasterPassword/ObjC/iOS/MPEmergencySegue.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPEmergencySegue.h
|
||||
// MPEmergencySegue
|
||||
//
|
||||
// Created by lhunath on 2014-04-09.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MPEmergencySegue : UIStoryboardSegue
|
||||
|
||||
@property(nonatomic) BOOL unwind;
|
||||
@end
|
57
MasterPassword/ObjC/iOS/MPEmergencySegue.m
Normal file
57
MasterPassword/ObjC/iOS/MPEmergencySegue.m
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPEmergencySegue.h
|
||||
// MPEmergencySegue
|
||||
//
|
||||
// Created by lhunath on 2014-04-09.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPEmergencySegue.h"
|
||||
|
||||
@implementation MPEmergencySegue {
|
||||
}
|
||||
|
||||
- (void)perform {
|
||||
|
||||
UIViewController *sourceViewController = self.sourceViewController;
|
||||
UIViewController *destinationViewController = self.destinationViewController;
|
||||
|
||||
if (!self.unwind) {
|
||||
// Winding
|
||||
[sourceViewController addChildViewController:destinationViewController];
|
||||
[sourceViewController.view addSubview:destinationViewController.view];
|
||||
CGRectSetY(destinationViewController.view.bounds, sourceViewController.view.frame.size.height);
|
||||
[UIView transitionWithView:sourceViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent
|
||||
animations:^{
|
||||
CGRectSetY(destinationViewController.view.bounds, 0);
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[destinationViewController didMoveToParentViewController:sourceViewController];
|
||||
}];
|
||||
}
|
||||
else {
|
||||
// Unwinding
|
||||
[sourceViewController willMoveToParentViewController:nil];
|
||||
[UIView transitionWithView:sourceViewController.parentViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent
|
||||
animations:^{
|
||||
CGRectSetY(sourceViewController.view.bounds, sourceViewController.parentViewController.view.frame.size.height);
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[sourceViewController.view removeFromSuperview];
|
||||
[sourceViewController removeFromParentViewController];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
38
MasterPassword/ObjC/iOS/MPEmergencyViewController.h
Normal file
38
MasterPassword/ObjC/iOS/MPEmergencyViewController.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLGitTip.h"
|
||||
|
||||
@interface MPEmergencyViewController : UIViewController <UITextFieldDelegate>
|
||||
|
||||
@property(weak, nonatomic) IBOutlet UIView *dialogView;
|
||||
@property(weak, nonatomic) IBOutlet UIView *containerView;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *userNameField;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *masterPasswordField;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *siteField;
|
||||
@property(weak, nonatomic) IBOutlet UIStepper *counterStepper;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *counterLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *passwordLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIView *tipContainer;
|
||||
|
||||
- (IBAction)controlChanged:(UIControl *)control;
|
||||
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer;
|
||||
|
||||
@end
|
202
MasterPassword/ObjC/iOS/MPEmergencyViewController.m
Normal file
202
MasterPassword/ObjC/iOS/MPEmergencyViewController.m
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPEmergencyViewController.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
@implementation MPEmergencyViewController {
|
||||
MPKey *_key;
|
||||
NSOperationQueue *_emergencyKeyQueue;
|
||||
NSOperationQueue *_emergencyPasswordQueue;
|
||||
NSArray *_notificationObservers;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
[_emergencyKeyQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
|
||||
[_emergencyPasswordQueue = [NSOperationQueue new] setMaxConcurrentOperationCount:1];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
self.dialogView.layer.cornerRadius = 5;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self reset];
|
||||
[self registerObservers];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self reset];
|
||||
}
|
||||
|
||||
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
|
||||
|
||||
return [self respondsToSelector:action];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
|
||||
|
||||
dbg(@"unwindToCombined:%@", sender);
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
[textField resignFirstResponder];
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)controlChanged:(UIControl *)control {
|
||||
|
||||
if (control == self.userNameField || control == self.masterPasswordField)
|
||||
[self updateKey];
|
||||
else
|
||||
[self updatePassword];
|
||||
}
|
||||
|
||||
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer {
|
||||
|
||||
if (recognizer.state == UIGestureRecognizerStateEnded) {
|
||||
NSString *sitePassword = self.passwordLabel.text;
|
||||
if ([sitePassword length]) {
|
||||
[UIPasteboard generalPasteboard].string = sitePassword;
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.tipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
PearlMainQueueAfter( 3, ^{
|
||||
self.tipContainer.alpha = 0;
|
||||
} );
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateKey {
|
||||
|
||||
NSString *userName = self.userNameField.text;
|
||||
NSString *masterPassword = self.masterPasswordField.text;
|
||||
|
||||
self.passwordLabel.text = nil;
|
||||
[self.activity startAnimating];
|
||||
[_emergencyKeyQueue cancelAllOperations];
|
||||
[_emergencyKeyQueue addOperationWithBlock:^{
|
||||
if ([masterPassword length] && [userName length])
|
||||
_key = [MPAlgorithmDefault keyForPassword:masterPassword ofUserNamed:userName];
|
||||
else
|
||||
_key = nil;
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self updatePassword];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updatePassword {
|
||||
|
||||
NSString *siteName = self.siteField.text;
|
||||
MPElementType siteType = [self siteType];
|
||||
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
|
||||
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
|
||||
|
||||
self.passwordLabel.text = nil;
|
||||
[self.activity startAnimating];
|
||||
[_emergencyPasswordQueue cancelAllOperations];
|
||||
[_emergencyPasswordQueue addOperationWithBlock:^{
|
||||
NSString *sitePassword = nil;
|
||||
if (_key && [siteName length])
|
||||
sitePassword = [MPAlgorithmDefault generateContentNamed:siteName ofType:siteType withCounter:siteCounter usingKey:_key];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self.activity stopAnimating];
|
||||
self.passwordLabel.text = sitePassword;
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (enum MPElementType)siteType {
|
||||
|
||||
switch (self.typeControl.selectedSegmentIndex) {
|
||||
case 0:
|
||||
return MPElementTypeGeneratedMaximum;
|
||||
case 1:
|
||||
return MPElementTypeGeneratedLong;
|
||||
case 2:
|
||||
return MPElementTypeGeneratedMedium;
|
||||
case 3:
|
||||
return MPElementTypeGeneratedBasic;
|
||||
case 4:
|
||||
return MPElementTypeGeneratedShort;
|
||||
case 5:
|
||||
return MPElementTypeGeneratedPIN;
|
||||
default:
|
||||
Throw(@"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
|
||||
self.userNameField.text = nil;
|
||||
self.masterPasswordField.text = nil;
|
||||
self.siteField.text = nil;
|
||||
self.counterStepper.value = 1;
|
||||
self.typeControl.selectedSegmentIndex = 1;
|
||||
[self updateKey];
|
||||
}
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
if ([_notificationObservers count])
|
||||
return;
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self performSegueWithIdentifier:@"unwind-emergency" sender:self];
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
|
||||
for (id observer in _notificationObservers)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
_notificationObservers = nil;
|
||||
}
|
||||
|
||||
@end
|
@@ -28,12 +28,12 @@
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil
|
||||
queue:nil usingBlock:
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||
} );
|
||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.logView.contentInset = UIEdgeInsetsMake( 64, 0, 93, 0 );
|
||||
|
||||
[self refresh:nil];
|
||||
|
||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||
@@ -62,7 +64,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];
|
||||
} );
|
||||
@@ -105,8 +107,10 @@
|
||||
- (void)switchCloudStore {
|
||||
|
||||
NSDictionary *cloudStores = [[MPiOSAppDelegate get].storeManager enumerateCloudStores];
|
||||
if (!cloudStores)
|
||||
wrn(@"Failed enumerating cloud stores.");
|
||||
if (!cloudStores) {
|
||||
wrn( @"Failed enumerating cloud stores." );
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *currentStoreUUID = nil;
|
||||
NSMutableDictionary *stores = [NSMutableDictionary dictionary];
|
||||
|
@@ -715,11 +715,6 @@
|
||||
[self setHelpHidden:NO animated:YES];
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
inf(@"Action: Guide");
|
||||
[[MPiOSAppDelegate get] showGuide];
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
inf(@"Action: Preferences");
|
||||
[self performSegueWithIdentifier:@"MP_UserProfile" sender:self];
|
||||
@@ -776,7 +771,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;
|
||||
}];
|
||||
|
28
MasterPassword/ObjC/iOS/MPPasswordCell.h
Normal file
28
MasterPassword/ObjC/iOS/MPPasswordCell.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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 <Foundation/Foundation.h>
|
||||
#import "MPEntities.h"
|
||||
#import "MPCell.h"
|
||||
|
||||
@interface MPPasswordCell : MPCell
|
||||
|
||||
/** Populate our UI to reflect the current state. */
|
||||
- (void)updateAnimated:(BOOL)animated;
|
||||
|
||||
@end
|
75
MasterPassword/ObjC/iOS/MPPasswordCell.m
Normal file
75
MasterPassword/ObjC/iOS/MPPasswordCell.m
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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 "MPPasswordCell.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@implementation MPPasswordCell
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
- (void)prepareForReuse {
|
||||
|
||||
[super prepareForReuse];
|
||||
[self updateAnimated:NO];
|
||||
}
|
||||
|
||||
// Unblocks animations for all CALayer properties (eg. shadowOpacity)
|
||||
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
|
||||
|
||||
id<CAAction> defaultAction = [super actionForLayer:layer forKey:event];
|
||||
if (defaultAction == (id)[NSNull null] && [event isEqualToString:@"position"])
|
||||
return defaultAction;
|
||||
|
||||
return NSNullToNil(defaultAction);
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setSelected:(BOOL)selected {
|
||||
|
||||
[super setSelected:selected];
|
||||
|
||||
[self updateAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted {
|
||||
|
||||
[super setHighlighted:highlighted];
|
||||
|
||||
[self updateAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated {
|
||||
|
||||
if (![NSThread isMainThread]) {
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self updateAnimated:animated];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.layer.shadowOpacity = self.selected? 1: self.highlighted? 0.3f: 0;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
50
MasterPassword/ObjC/iOS/MPPasswordLargeCell.h
Normal file
50
MasterPassword/ObjC/iOS/MPPasswordLargeCell.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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 <Foundation/Foundation.h>
|
||||
#import "MPEntities.h"
|
||||
#import "MPCell.h"
|
||||
#import "MPPasswordCell.h"
|
||||
|
||||
typedef NS_ENUM (NSUInteger, MPContentFieldMode) {
|
||||
MPContentFieldModePassword,
|
||||
MPContentFieldModeUser,
|
||||
};
|
||||
|
||||
@interface MPPasswordLargeCell : MPPasswordCell <UITextFieldDelegate>
|
||||
|
||||
@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;
|
||||
@property(nonatomic, strong) IBOutlet UILabel *nameLabel;
|
||||
@property(nonatomic, strong) IBOutlet UIButton *loginButton;
|
||||
|
||||
+ (instancetype)dequeueCellWithType:(MPElementType)type fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (void)update;
|
||||
- (void)updateWithElement:(MPElementEntity *)mainElement;
|
||||
- (void)updateWithTransientSite:(NSString *)siteName;
|
||||
|
||||
- (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
|
235
MasterPassword/ObjC/iOS/MPPasswordLargeCell.m
Normal file
235
MasterPassword/ObjC/iOS/MPPasswordLargeCell.m
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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
|
||||
|
||||
#pragma mark - Life
|
||||
|
||||
+ (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;
|
||||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
|
||||
[super awakeFromNib];
|
||||
|
||||
self.layer.cornerRadius = 5;
|
||||
self.layer.shadowOffset = CGSizeZero;
|
||||
self.layer.shadowRadius = 5;
|
||||
self.layer.shadowOpacity = 0;
|
||||
self.layer.shadowColor = [UIColor whiteColor].CGColor;
|
||||
|
||||
[self prepareForReuse];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
|
||||
_contentFieldMode = 0;
|
||||
self.contentField.text = nil;
|
||||
|
||||
[super prepareForReuse];
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
|
||||
self.loginButton.alpha = 0;
|
||||
self.upgradeButton.alpha = 0;
|
||||
self.nameLabel.text = @"";
|
||||
self.typeLabel.text = @"";
|
||||
self.contentField.text = @"";
|
||||
self.contentField.placeholder = nil;
|
||||
self.contentField.enabled = self.contentFieldMode == MPContentFieldModeUser;
|
||||
self.loginButton.selected = self.contentFieldMode == MPContentFieldModeUser;
|
||||
|
||||
switch (self.contentFieldMode) {
|
||||
case MPContentFieldModePassword: {
|
||||
if (self.type & MPElementTypeClassStored)
|
||||
self.contentField.placeholder = strl( @"Set custom password" );
|
||||
else if (self.type & MPElementTypeClassGenerated)
|
||||
self.contentField.placeholder = strl( @"Generating..." );
|
||||
break;
|
||||
}
|
||||
case MPContentFieldModeUser: {
|
||||
self.contentField.placeholder = strl( @"Enter your login name" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateWithTransientSite:(NSString *)siteName {
|
||||
|
||||
[self update];
|
||||
|
||||
self.nameLabel.text = strl( @"%@ - Tap to create", siteName );
|
||||
self.typeLabel.text = [MPAlgorithmDefault nameOfType:self.type];
|
||||
|
||||
[self resolveContentOfCellTypeForTransientSite:siteName usingKey:[MPiOSAppDelegate get].key result:^(NSString *string) {
|
||||
PearlMainQueue( ^{ self.contentField.text = string; } );
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateWithElement:(MPElementEntity *)mainElement {
|
||||
|
||||
[self update];
|
||||
|
||||
if (!mainElement)
|
||||
return;
|
||||
|
||||
self.loginButton.alpha = 1;
|
||||
if (mainElement.requiresExplicitMigration)
|
||||
self.upgradeButton.alpha = 1;
|
||||
|
||||
self.nameLabel.text = mainElement.name;
|
||||
if (self.type == (MPElementType)NSNotFound)
|
||||
self.typeLabel.text = @"Delete";
|
||||
else
|
||||
self.typeLabel.text = [mainElement.algorithm nameOfType:self.type];
|
||||
|
||||
switch (self.contentFieldMode) {
|
||||
case MPContentFieldModePassword: {
|
||||
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.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 - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
[textField resignFirstResponder];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
|
||||
if (textField == self.contentField) {
|
||||
NSString *newContent = textField.text;
|
||||
textField.enabled = NO;
|
||||
|
||||
if (self.contentFieldMode == MPContentFieldModeUser)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPElementEntity *element = [[MPPasswordTypesCell findAsSuperviewOf:self] elementInContext:context];
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
element.loginName = newContent;
|
||||
[context saveToStore];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self updateAnimated:YES];
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Updated" dismissAfter:2];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#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 ([[[MPPasswordTypesCell findAsSuperviewOf:self] elementInContext:context] migrateExplicitly:YES]) {
|
||||
[context saveToStore];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[[MPPasswordTypesCell 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;
|
||||
|
||||
[[MPPasswordTypesCell findAsSuperviewOf:self] reloadData];
|
||||
}
|
||||
|
||||
@end
|
24
MasterPassword/ObjC/iOS/MPPasswordLargeDeleteCell.h
Normal file
24
MasterPassword/ObjC/iOS/MPPasswordLargeDeleteCell.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordLargeDeleteCell.h
|
||||
// MPPasswordLargeDeleteCell
|
||||
//
|
||||
// Created by lhunath on 2014-03-19.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPPasswordLargeCell.h"
|
||||
|
||||
@interface MPPasswordLargeDeleteCell : MPPasswordLargeCell
|
||||
|
||||
@end
|
30
MasterPassword/ObjC/iOS/MPPasswordLargeDeleteCell.m
Normal file
30
MasterPassword/ObjC/iOS/MPPasswordLargeDeleteCell.m
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordLargeDeleteCell.h
|
||||
// MPPasswordLargeDeleteCell
|
||||
//
|
||||
// Created by lhunath on 2014-03-19.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPasswordLargeDeleteCell.h"
|
||||
|
||||
@implementation MPPasswordLargeDeleteCell
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (MPElementEntity *)saveContentTypeWithElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@end
|
27
MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.h
Normal file
27
MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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 <Foundation/Foundation.h>
|
||||
#import "MPPasswordLargeCell.h"
|
||||
|
||||
@interface MPPasswordLargeGeneratedCell : MPPasswordLargeCell
|
||||
|
||||
@property(strong, nonatomic) IBOutlet UILabel *counterLabel;
|
||||
@property(strong, nonatomic) IBOutlet UIButton *counterButton;
|
||||
|
||||
@end
|
139
MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.m
Normal file
139
MasterPassword/ObjC/iOS/MPPasswordLargeGeneratedCell.m
Normal file
@@ -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 <lhunath@lyndir.com>
|
||||
* @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 "MPPasswordTypesCell.h"
|
||||
|
||||
@implementation MPPasswordLargeGeneratedCell
|
||||
|
||||
- (void)awakeFromNib {
|
||||
|
||||
[super awakeFromNib];
|
||||
|
||||
UILongPressGestureRecognizer *gestureRecognizer = [[UILongPressGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(doResetCounterRecognizer:)];
|
||||
[self.counterButton addGestureRecognizer:gestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)updateWithElement:(MPElementEntity *)mainElement {
|
||||
|
||||
[super updateWithElement: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<MPAlgorithm> 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:[[MPPasswordTypesCell findAsSuperviewOf:self] elementInContext:context]];
|
||||
}
|
||||
|
||||
- (MPElementGeneratedEntity *)generatedElement:(MPElementEntity *)element {
|
||||
|
||||
if (![element isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
return nil;
|
||||
|
||||
return (MPElementGeneratedEntity *)element;
|
||||
}
|
||||
|
||||
@end
|
24
MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.h
Normal file
24
MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordLargeStoredCell.h
|
||||
// MPPasswordLargeStoredCell
|
||||
//
|
||||
// Created by lhunath on 2014-03-19.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPPasswordLargeCell.h"
|
||||
|
||||
@interface MPPasswordLargeStoredCell : MPPasswordLargeCell
|
||||
|
||||
@end
|
103
MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.m
Normal file
103
MasterPassword/ObjC/iOS/MPPasswordLargeStoredCell.m
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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 "MPPasswordTypesCell.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];
|
||||
[storedElement.algorithm saveContent:self.contentField.text toElement:storedElement usingKey:[MPiOSAppDelegate get].key];
|
||||
[context saveToStore];
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)doEditContent:(UIButton *)sender {
|
||||
|
||||
UITextField *textField = self.contentField;
|
||||
textField.enabled = YES;
|
||||
[textField becomeFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
|
||||
[super textFieldDidEndEditing:textField];
|
||||
|
||||
if (textField == self.contentField) {
|
||||
NSString *newContent = textField.text;
|
||||
|
||||
if (self.contentFieldMode == MPContentFieldModePassword)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPElementStoredEntity *storedElement = [self storedElementInContext:context];
|
||||
if (!storedElement)
|
||||
return;
|
||||
|
||||
[storedElement.algorithm saveContent:newContent toElement:storedElement usingKey:[MPiOSAppDelegate get].key];
|
||||
[context saveToStore];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self updateAnimated:YES];
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (MPElementStoredEntity *)storedElementInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
return [self storedElement:[[MPPasswordTypesCell findAsSuperviewOf:self] elementInContext:context]];
|
||||
}
|
||||
|
||||
- (MPElementStoredEntity *)storedElement:(MPElementEntity *)element {
|
||||
|
||||
if (![element isKindOfClass:[MPElementStoredEntity class]])
|
||||
return nil;
|
||||
|
||||
return (MPElementStoredEntity *)element;
|
||||
}
|
||||
|
||||
@end
|
44
MasterPassword/ObjC/iOS/MPPasswordTypesCell.h
Normal file
44
MasterPassword/ObjC/iOS/MPPasswordTypesCell.h
Normal file
@@ -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 <lhunath@lyndir.com>
|
||||
* @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 <Foundation/Foundation.h>
|
||||
#import "MPCell.h"
|
||||
#import "MPPasswordCell.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
|
||||
@interface MPPasswordTypesCell : MPPasswordCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
|
||||
@property(nonatomic, strong) IBOutlet UICollectionView *contentCollectionView;
|
||||
|
||||
@property(nonatomic, weak) MPPasswordsViewController *passwordsViewController;
|
||||
@property(nonatomic, copy) NSString *transientSite;
|
||||
|
||||
@property(nonatomic, strong) id<MPAlgorithm> algorithm;
|
||||
@property(nonatomic) MPElementType activeType;
|
||||
|
||||
- (MPElementEntity *)mainElement;
|
||||
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context;
|
||||
- (void)setElement:(MPElementEntity *)element;
|
||||
- (void)reloadData;
|
||||
|
||||
+ (instancetype)dequeueCellForElement:(MPElementEntity *)element
|
||||
fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
|
||||
+ (instancetype)dequeueCellForTransientSite:(NSString *)siteName
|
||||
fromCollectionView:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
371
MasterPassword/ObjC/iOS/MPPasswordTypesCell.m
Normal file
371
MasterPassword/ObjC/iOS/MPPasswordTypesCell.m
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @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"
|
||||
#import "MPPasswordLargeDeleteCell.h"
|
||||
|
||||
@implementation MPPasswordTypesCell {
|
||||
NSManagedObjectID *_elementOID;
|
||||
}
|
||||
|
||||
#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 {
|
||||
|
||||
_elementOID = nil;
|
||||
_transientSite = nil;
|
||||
_activeType = 0;
|
||||
_algorithm = MPAlgorithmDefault;
|
||||
|
||||
[super prepareForReuse];
|
||||
}
|
||||
|
||||
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
|
||||
|
||||
[super applyLayoutAttributes:layoutAttributes];
|
||||
|
||||
[self.contentCollectionView.collectionViewLayout invalidateLayout];
|
||||
[self scrollToActiveType];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
return collectionView.bounds.size;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
if (!self.algorithm)
|
||||
dbg_return_tr( 0, @, @(section) );
|
||||
|
||||
if (self.transientSite)
|
||||
dbg_return_tr( [[self.algorithm allTypes] count], @, @(section) );
|
||||
|
||||
dbg_return_tr( [[self.algorithm allTypes] count] + 1 /* Delete */, @, @(section) );
|
||||
}
|
||||
|
||||
- (MPPasswordLargeCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
MPPasswordLargeCell *cell;
|
||||
if (indexPath.item == 0)
|
||||
cell = [MPPasswordLargeDeleteCell dequeueCellWithType:(MPElementType)NSNotFound fromCollectionView:collectionView
|
||||
atIndexPath:indexPath];
|
||||
else
|
||||
cell = [MPPasswordLargeCell dequeueCellWithType:[self typeForContentIndexPath:indexPath] fromCollectionView:collectionView
|
||||
atIndexPath:indexPath];
|
||||
|
||||
if (self.transientSite)
|
||||
[cell updateWithTransientSite:self.transientSite];
|
||||
else
|
||||
[cell updateWithElement:self.mainElement];
|
||||
|
||||
dbg_return( cell, indexPath );
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegateFlowLayout
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
NSString *newSiteName = self.transientSite;
|
||||
if (newSiteName) {
|
||||
[[UIResponder findFirstResponder] resignFirstResponder];
|
||||
[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
|
||||
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
|
||||
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
// Create
|
||||
[[MPiOSAppDelegate get] addElementNamed:newSiteName completion:^(MPElementEntity *element) {
|
||||
[self copyContentOfElement:element];
|
||||
PearlMainQueue( ^{
|
||||
[self.passwordsViewController updatePasswords];
|
||||
} );
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL used = NO;
|
||||
MPElementEntity *element = [self elementInContext:context];
|
||||
if (!element)
|
||||
wrn(@"No element to use for: %@", self);
|
||||
else if (indexPath.item == 0) {
|
||||
[context deleteObject:element];
|
||||
[context saveToStore];
|
||||
} else
|
||||
used = [self copyContentOfElement:element];
|
||||
|
||||
PearlMainQueueAfter( 0.2f, ^{
|
||||
for (NSIndexPath *selectedIndexPath in [collectionView indexPathsForSelectedItems])
|
||||
[collectionView deselectItemAtIndexPath:selectedIndexPath animated:YES];
|
||||
|
||||
if (used)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context_) {
|
||||
[[self elementInContext:context_] use];
|
||||
[context_ saveToStore];
|
||||
}];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)copyContentOfElement:(MPElementEntity *)element {
|
||||
|
||||
inf( @"Copying password for: %@", element.name );
|
||||
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
|
||||
@"type" : NilToNSNull( element.typeName ),
|
||||
@"version" : @(element.version),
|
||||
@"emergency" : @NO
|
||||
} );
|
||||
|
||||
NSString *result = [element resolveContentUsingKey:[MPAppDelegate_Shared get].key];
|
||||
if ([result length]) {
|
||||
[UIPasteboard generalPasteboard].string = result;
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Copied" dismissAfter:2];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
- (void)reloadData {
|
||||
|
||||
if (self.transientSite)
|
||||
PearlMainQueue( ^{
|
||||
self.activeType = IfElse( [[MPiOSAppDelegate get] activeUserForMainThread].defaultType, MPElementTypeGeneratedLong );
|
||||
|
||||
for (NSInteger section = 0; section < [self.contentCollectionView numberOfSections]; ++section)
|
||||
for (NSInteger item = 0; item < [self.contentCollectionView numberOfItemsInSection:section]; ++item)
|
||||
[(MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:
|
||||
[NSIndexPath indexPathForItem:item inSection:section]] updateWithTransientSite:self.transientSite];
|
||||
|
||||
} );
|
||||
else
|
||||
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
||||
MPElementEntity *mainElement = [self mainElement];
|
||||
|
||||
self.algorithm = IfNotNilElse( mainElement.algorithm, MPAlgorithmDefault );
|
||||
self.activeType = mainElement.type;
|
||||
|
||||
for (NSInteger section = 0; section < [self.contentCollectionView numberOfSections]; ++section)
|
||||
for (NSInteger item = 0; item < [self.contentCollectionView numberOfItemsInSection:section]; ++item)
|
||||
[(MPPasswordLargeCell *)[self.contentCollectionView cellForItemAtIndexPath:
|
||||
[NSIndexPath indexPathForItem:item inSection:section]] updateWithElement:mainElement];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)scrollToActiveType {
|
||||
|
||||
if (self.activeType && self.activeType != (MPElementType)NSNotFound)
|
||||
[self.contentCollectionView scrollToItemAtIndexPath:[self contentIndexPathForType:self.activeType]
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
|
||||
}
|
||||
|
||||
- (MPElementType)typeForContentIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (self.transientSite)
|
||||
return [[self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN][indexPath.item] unsignedIntegerValue];
|
||||
|
||||
if (indexPath.item == 0)
|
||||
return (MPElementType)NSNotFound;
|
||||
|
||||
return [[self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN][indexPath.item - 1] unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)contentIndexPathForType:(MPElementType)type {
|
||||
|
||||
NSArray *types = [self.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
|
||||
for (NSInteger t = 0; t < [types count]; ++t)
|
||||
if ([types[t] unsignedIntegerValue] == type) {
|
||||
if (self.transientSite)
|
||||
return [NSIndexPath indexPathForItem:t inSection:0];
|
||||
else
|
||||
return [NSIndexPath indexPathForItem:t + 1 inSection:0];
|
||||
}
|
||||
|
||||
Throw(@"Unsupported type: %lud", (long)type);
|
||||
}
|
||||
|
||||
- (void)saveContentType {
|
||||
|
||||
CGPoint centerPoint = CGPointFromCGRectCenter( self.contentCollectionView.bounds );
|
||||
NSIndexPath *centerIndexPath = [self.contentCollectionView indexPathForItemAtPoint:centerPoint];
|
||||
MPElementType type = [self typeForContentIndexPath:centerIndexPath];
|
||||
if (type == ((MPElementType)NSNotFound))
|
||||
// Active cell is not a type cell.
|
||||
return;
|
||||
|
||||
self.activeType = type;
|
||||
|
||||
if (self.transientSite)
|
||||
return;
|
||||
|
||||
[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 || element.type == cell.type)
|
||||
// Nothing changed.
|
||||
return;
|
||||
|
||||
self.element = [cell saveContentTypeWithElement:element saveInContext:context];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
- (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 {
|
||||
|
||||
NSManagedObjectID *newElementOID = element.objectID;
|
||||
NSAssert( !newElementOID.isTemporaryID, @"Element doesn't have a permanent objectID: %@", element );
|
||||
if ([_elementOID isEqual:newElementOID])
|
||||
return;
|
||||
|
||||
dbg( @"element: %@ -> %@", _elementOID, newElementOID );
|
||||
|
||||
_transientSite = nil;
|
||||
_elementOID = newElementOID;
|
||||
|
||||
[self updateAnimated:YES];
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
- (MPElementEntity *)mainElement {
|
||||
|
||||
return [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||
}
|
||||
|
||||
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
return [MPElementEntity existingObjectWithID:_elementOID inContext:context];
|
||||
}
|
||||
|
||||
- (void)setActiveType:(MPElementType)activeType {
|
||||
|
||||
_activeType = activeType;
|
||||
|
||||
[self scrollToActiveType];
|
||||
}
|
||||
|
||||
- (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<MPAlgorithm>)algorithm {
|
||||
|
||||
if ([_algorithm isEqual:algorithm])
|
||||
return;
|
||||
|
||||
_algorithm = algorithm;
|
||||
|
||||
[self.contentCollectionView reloadData];
|
||||
}
|
||||
|
||||
@end
|
23
MasterPassword/ObjC/iOS/MPPasswordsCoachmarkViewController.h
Normal file
23
MasterPassword/ObjC/iOS/MPPasswordsCoachmarkViewController.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordsCoachmarkViewController.h
|
||||
// MPPasswordsCoachmarkViewController
|
||||
//
|
||||
// Created by lhunath on 2014-04-23.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPCoachmarkViewController.h"
|
||||
|
||||
@interface MPPasswordsCoachmarkViewController : MPCoachmarkViewController <UICollectionViewDataSource>
|
||||
@end
|
51
MasterPassword/ObjC/iOS/MPPasswordsCoachmarkViewController.m
Normal file
51
MasterPassword/ObjC/iOS/MPPasswordsCoachmarkViewController.m
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordsCoachmarkViewController.h
|
||||
// MPPasswordsCoachmarkViewController
|
||||
//
|
||||
// Created by lhunath on 2014-04-23.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPasswordsCoachmarkViewController.h"
|
||||
#import "MPPasswordLargeGeneratedCell.h"
|
||||
#import "MPPasswordLargeStoredCell.h"
|
||||
|
||||
@implementation MPPasswordsCoachmarkViewController
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.item == 0) {
|
||||
MPPasswordLargeGeneratedCell *cell = [MPPasswordLargeGeneratedCell dequeueCellWithType:MPElementTypeGeneratedLong
|
||||
fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
[cell updateWithTransientSite:@"apple.com"];
|
||||
|
||||
return cell;
|
||||
}
|
||||
else if (indexPath.item == 1) {
|
||||
MPPasswordLargeStoredCell *cell = [MPPasswordLargeStoredCell dequeueCellWithType:MPElementTypeStoredPersonal
|
||||
fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
[cell updateWithTransientSite:@"gmail.com"];
|
||||
[cell.contentField setText:@"PaS$w0rD"];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
Throw(@"Unexpected item for indexPath: %@", indexPath);
|
||||
}
|
||||
|
||||
@end
|
25
MasterPassword/ObjC/iOS/MPPasswordsSegue.h
Normal file
25
MasterPassword/ObjC/iOS/MPPasswordsSegue.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordsSegue.h
|
||||
// MPPasswordsSegue
|
||||
//
|
||||
// Created by lhunath on 2014-04-12.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MPPasswordsSegue : UIStoryboardSegue
|
||||
|
||||
@property (nonatomic, assign) BOOL animated;
|
||||
|
||||
@end
|
72
MasterPassword/ObjC/iOS/MPPasswordsSegue.m
Normal file
72
MasterPassword/ObjC/iOS/MPPasswordsSegue.m
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordsSegue.h
|
||||
// MPPasswordsSegue
|
||||
//
|
||||
// Created by lhunath on 2014-04-12.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPasswordsSegue.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
#import "MPCombinedViewController.h"
|
||||
|
||||
@implementation MPPasswordsSegue {
|
||||
}
|
||||
|
||||
- (id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
|
||||
|
||||
if (!(self = [super initWithIdentifier:identifier source:source destination:destination]))
|
||||
return nil;
|
||||
|
||||
self.animated = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)perform {
|
||||
|
||||
if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
|
||||
__weak MPPasswordsViewController *passwordsVC = self.destinationViewController;
|
||||
MPCombinedViewController *combinedVC = self.sourceViewController;
|
||||
[combinedVC addChildViewController:passwordsVC];
|
||||
passwordsVC.active = NO;
|
||||
|
||||
UIView *passwordsView = passwordsVC.view;
|
||||
passwordsView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersView];
|
||||
[combinedVC.view addConstraintsWithVisualFormats:@[ @"H:|[passwordsView]|", @"V:|[passwordsView]|" ]
|
||||
options:0 metrics:nil
|
||||
views:NSDictionaryOfVariableBindings( passwordsView )];
|
||||
|
||||
[passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) {
|
||||
if (!finished)
|
||||
return;
|
||||
|
||||
[passwordsVC didMoveToParentViewController:combinedVC];
|
||||
}];
|
||||
}
|
||||
else if ([self.sourceViewController isKindOfClass:[MPPasswordsViewController class]]) {
|
||||
__weak MPPasswordsViewController *passwordsVC = self.sourceViewController;
|
||||
|
||||
[passwordsVC willMoveToParentViewController:nil];
|
||||
[passwordsVC setActive:NO animated:self.animated completion:^(BOOL finished) {
|
||||
if (!finished)
|
||||
return;
|
||||
|
||||
[passwordsVC.view removeFromSuperview];
|
||||
[passwordsVC removeFromParentViewController];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
44
MasterPassword/ObjC/iOS/MPPasswordsViewController.h
Normal file
44
MasterPassword/ObjC/iOS/MPPasswordsViewController.h
Normal file
@@ -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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLGitTip.h"
|
||||
@class MPElementEntity;
|
||||
@class MPCoachmark;
|
||||
|
||||
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
|
||||
@property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer;
|
||||
@property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView;
|
||||
@property(strong, nonatomic) IBOutlet UISearchBar *passwordsSearchBar;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
|
||||
@property(strong, nonatomic) IBOutlet UIView *popdownView;
|
||||
@property(strong, nonatomic) IBOutlet UIView *popdownContainer;
|
||||
|
||||
@property(assign, nonatomic) BOOL active;
|
||||
|
||||
@property(nonatomic, readonly) MPCoachmark *coachmark;
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
|
||||
- (void)updatePasswords;
|
||||
|
||||
- (IBAction)dismissPopdown:(id)sender;
|
||||
- (IBAction)signOut:(id)sender;
|
||||
|
||||
@end
|
434
MasterPassword/ObjC/iOS/MPPasswordsViewController.m
Normal file
434
MasterPassword/ObjC/iOS/MPPasswordsViewController.m
Normal file
@@ -0,0 +1,434 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordsViewController.h
|
||||
// MPPasswordsViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPasswordsViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPPasswordLargeCell.h"
|
||||
#import "MPPasswordTypesCell.h"
|
||||
#import "MPPopdownSegue.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPCoachmarkViewController.h"
|
||||
|
||||
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
|
||||
|
||||
@property(nonatomic, strong) IBOutlet UINavigationBar *navigationBar;
|
||||
@property(nonatomic, readonly) NSString *query;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPPasswordsViewController {
|
||||
__weak id _storeObserver;
|
||||
__weak id _mocObserver;
|
||||
NSArray *_notificationObservers;
|
||||
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
|
||||
NSFetchedResultsController *_fetchedResultsController;
|
||||
BOOL _exactMatch;
|
||||
UIColor *_backgroundColor;
|
||||
UIColor *_darkenedBackgroundColor;
|
||||
__weak UIViewController *_popdownVC;
|
||||
}
|
||||
|
||||
#pragma mark - Life
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
||||
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
||||
_coachmark = [MPCoachmark coachmarkForClass:[self class] version:0];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self registerObservers];
|
||||
[self observeStore];
|
||||
[self updateFromConfig];
|
||||
[self updatePasswords];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
PearlMainQueueAfter( 1, ^{
|
||||
if (!self.coachmark.coached)
|
||||
[self performSegueWithIdentifier:@"coachmarks" sender:self];
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self stopObservingStore];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"popdown"])
|
||||
_popdownVC = segue.destinationViewController;
|
||||
if ([segue.identifier isEqualToString:@"coachmarks"])
|
||||
((MPCoachmarkViewController *)segue.destinationViewController).coachmark = self.coachmark;
|
||||
}
|
||||
|
||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
||||
|
||||
[self.passwordCollectionView.collectionViewLayout invalidateLayout];
|
||||
|
||||
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegateFlowLayout
|
||||
|
||||
- (CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
referenceSizeForHeaderInSection:(NSInteger)section {
|
||||
|
||||
return CGSizeMake( collectionView.bounds.size.width, CGPointFromCGRectBottom( self.passwordsSearchBar.frame ).y );
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.passwordCollectionView) {
|
||||
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
|
||||
CGFloat itemWidth = UIEdgeInsetsInsetRect(self.passwordCollectionView.bounds, layout.sectionInset).size.width;
|
||||
return CGSizeMake( itemWidth, 100 );
|
||||
}
|
||||
|
||||
Throw(@"Unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||
|
||||
if (collectionView == self.passwordCollectionView)
|
||||
return [self.fetchedResultsController.sections count];
|
||||
|
||||
Throw(@"Unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
if (collectionView == self.passwordCollectionView)
|
||||
return ![MPiOSAppDelegate get].activeUserOID? 0:
|
||||
((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[section]).numberOfObjects +
|
||||
(!_exactMatch && [[self query] length]? 1: 0);
|
||||
|
||||
Throw(@"Unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.passwordCollectionView) {
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
MPPasswordTypesCell *cell;
|
||||
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects) {
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
cell = [MPPasswordTypesCell dequeueCellForElement:element fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
}
|
||||
else
|
||||
// New Site.
|
||||
cell = [MPPasswordTypesCell dequeueCellForTransientSite:self.query fromCollectionView:collectionView atIndexPath:indexPath];
|
||||
cell.passwordsViewController = self;
|
||||
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
dbg_return(cell, indexPath);
|
||||
}
|
||||
|
||||
Throw(@"Unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
|
||||
atIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
return [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MPPasswordHeader" forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
#pragma mark - NSFetchedResultsControllerDelegate
|
||||
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
|
||||
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
|
||||
|
||||
if (controller == _fetchedResultsController) {
|
||||
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]];
|
||||
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
|
||||
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
|
||||
|
||||
if (controller == _fetchedResultsController)
|
||||
[self.passwordCollectionView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
|
||||
|
||||
if (searchBar == self.passwordsSearchBar) {
|
||||
searchBar.text = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
|
||||
|
||||
if (searchBar == self.passwordsSearchBar)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.passwordCollectionView.backgroundColor = _darkenedBackgroundColor;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
|
||||
|
||||
if (searchBar == self.passwordsSearchBar) {
|
||||
self.passwordsSearchBar.showsCancelButton = NO;
|
||||
if (_passwordsDismissRecognizer)
|
||||
[self.view removeGestureRecognizer:_passwordsDismissRecognizer];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.passwordCollectionView.backgroundColor = _backgroundColor;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
[searchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
|
||||
if (searchBar == self.passwordsSearchBar)
|
||||
[self updatePasswords];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
if ([_notificationObservers count])
|
||||
return;
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
self.passwordSelectionContainer.alpha = 0;
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:MPSignedOutNotification object:nil
|
||||
queue:nil usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
_fetchedResultsController = nil;
|
||||
self.passwordsSearchBar.text = nil;
|
||||
[self updatePasswords];
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self updatePasswords];
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.passwordSelectionContainer.alpha = 1;
|
||||
}];
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:MPCheckConfigNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
[self updateFromConfig];
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
|
||||
for (id observer in _notificationObservers)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
_notificationObservers = nil;
|
||||
}
|
||||
|
||||
- (void)observeStore {
|
||||
|
||||
Weakify(self);
|
||||
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!_mocObserver && mainContext)
|
||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
// Strongify(self);
|
||||
// [self updatePasswords];
|
||||
}];
|
||||
if (!_storeObserver)
|
||||
_storeObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
_fetchedResultsController = nil;
|
||||
[self updatePasswords];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopObservingStore {
|
||||
|
||||
if (_mocObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
||||
if (_storeObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
|
||||
}
|
||||
|
||||
- (void)updateFromConfig {
|
||||
|
||||
self.passwordsSearchBar.keyboardType = [[MPiOSConfig get].dictationSearch boolValue]? UIKeyboardTypeDefault: UIKeyboardTypeURL;
|
||||
}
|
||||
|
||||
- (void)updatePasswords {
|
||||
|
||||
NSString *query = self.query;
|
||||
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
|
||||
if (!activeUserOID) {
|
||||
self.passwordsSearchBar.text = nil;
|
||||
PearlMainQueue( ^{
|
||||
[self.passwordCollectionView reloadData];
|
||||
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES];
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
NSError *error = nil;
|
||||
self.fetchedResultsController.fetchRequest.predicate =
|
||||
[query length]?
|
||||
[NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]:
|
||||
[NSPredicate predicateWithFormat:@"user == %@", activeUserOID];
|
||||
if (![self.fetchedResultsController performFetch:&error])
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
_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:^(BOOL finished) {
|
||||
if (finished)
|
||||
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top )
|
||||
animated:YES];
|
||||
}];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSString *)query {
|
||||
|
||||
return [self.passwordsSearchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
|
||||
- (NSFetchedResultsController *)fetchedResultsController {
|
||||
|
||||
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:mainContext sectionNameKeyPath:nil cacheName:nil];
|
||||
_fetchedResultsController.delegate = self;
|
||||
}];
|
||||
[self observeStore];
|
||||
}
|
||||
|
||||
return _fetchedResultsController;
|
||||
}
|
||||
|
||||
- (void)setActive:(BOOL)active {
|
||||
|
||||
[self setActive:active animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
|
||||
|
||||
_active = active;
|
||||
|
||||
[UIView animateWithDuration:animated? 0.4f: 0 animations:^{
|
||||
self.navigationBarToTopConstraint.priority = active? 1: UILayoutPriorityDefaultHigh;
|
||||
self.passwordsToBottomConstraint.priority = active? 1: UILayoutPriorityDefaultHigh;
|
||||
|
||||
[self.navigationBarToTopConstraint apply];
|
||||
[self.passwordsToBottomConstraint apply];
|
||||
} completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)dismissPopdown:(id)sender {
|
||||
|
||||
if (_popdownVC)
|
||||
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:_popdownVC destination:self] perform];
|
||||
else
|
||||
self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
}
|
||||
|
||||
- (IBAction)signOut:(id)sender {
|
||||
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
}
|
||||
|
||||
@end
|
22
MasterPassword/ObjC/iOS/MPPopdownSegue.h
Normal file
22
MasterPassword/ObjC/iOS/MPPopdownSegue.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPopdownSegue.h
|
||||
// MPPopdownSegue
|
||||
//
|
||||
// Created by lhunath on 2014-04-17.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MPPopdownSegue : UIStoryboardSegue
|
||||
@end
|
65
MasterPassword/ObjC/iOS/MPPopdownSegue.m
Normal file
65
MasterPassword/ObjC/iOS/MPPopdownSegue.m
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPopdownSegue.h
|
||||
// MPPopdownSegue
|
||||
//
|
||||
// Created by lhunath on 2014-04-17.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPopdownSegue.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
|
||||
@implementation MPPopdownSegue {
|
||||
}
|
||||
|
||||
- (void)perform {
|
||||
|
||||
MPPasswordsViewController *passwordsVC;
|
||||
UIViewController *popdownVC;
|
||||
if ([self.sourceViewController isKindOfClass:[MPPasswordsViewController class]]) {
|
||||
passwordsVC = self.sourceViewController;
|
||||
popdownVC = self.destinationViewController;
|
||||
UIView *popdownView = popdownVC.view;
|
||||
popdownView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[passwordsVC addChildViewController:popdownVC];
|
||||
[passwordsVC.popdownContainer addSubview:popdownView];
|
||||
[passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0
|
||||
metrics:nil views:NSDictionaryOfVariableBindings(popdownView)];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
passwordsVC.popdownToTopConstraint.priority = 1;
|
||||
[passwordsVC.popdownToTopConstraint apply];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[popdownVC didMoveToParentViewController:passwordsVC];
|
||||
}];
|
||||
}
|
||||
else if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
|
||||
popdownVC = self.sourceViewController;
|
||||
passwordsVC = self.destinationViewController;
|
||||
|
||||
[popdownVC willMoveToParentViewController:nil];
|
||||
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
|
||||
passwordsVC.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
[passwordsVC.popdownToTopConstraint apply];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[popdownVC.view removeFromSuperview];
|
||||
[popdownVC removeFromParentViewController];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@@ -9,15 +9,20 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "MPTypeViewController.h"
|
||||
|
||||
@interface MPPreferencesViewController : UITableViewController<MPTypeDelegate>
|
||||
@interface MPPreferencesViewController : UITableViewController
|
||||
|
||||
@property(weak, nonatomic) IBOutlet UIScrollView *avatarsView;
|
||||
@property(weak, nonatomic) IBOutlet UIButton *avatarTemplate;
|
||||
@property(weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *signOutCell;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *feedbackCell;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *coachmarksCell;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *changeMPCell;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *defaultTypeLabel;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *checkInconsistencies;
|
||||
@property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *generatedTypeControl;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *storedTypeControl;
|
||||
|
||||
- (IBAction)didToggleSwitch:(UISwitch *)sender;
|
||||
- (IBAction)previousAvatar:(id)sender;
|
||||
- (IBAction)nextAvatar:(id)sender;
|
||||
- (IBAction)valueChanged:(id)sender;
|
||||
|
||||
@end
|
||||
|
@@ -6,12 +6,13 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import "MPPreferencesViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "UIColor+Expanded.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
#import "MPCoachmarkViewController.h"
|
||||
|
||||
@interface MPPreferencesViewController()
|
||||
|
||||
@@ -21,158 +22,202 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.avatarTemplate.hidden = YES;
|
||||
|
||||
for (int a = 0; a < MPAvatarCount; ++a) {
|
||||
UIButton *avatar = [self.avatarTemplate clone];
|
||||
avatar.tag = a;
|
||||
avatar.hidden = NO;
|
||||
avatar.center = CGPointMake(
|
||||
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
|
||||
self.avatarTemplate.center.y );
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%d", a )]
|
||||
forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
|
||||
|
||||
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
|
||||
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
avatar.layer.shadowOpacity = 1;
|
||||
avatar.layer.shadowRadius = 5;
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
|
||||
if (highlighted || selected)
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
[[MPiOSAppDelegate get] activeUserInContext:moc].avatar = (unsigned)avatar.tag;
|
||||
[moc saveToStore];
|
||||
}];
|
||||
}
|
||||
} options:0];
|
||||
avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForMainThread].avatar);
|
||||
}
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
inf(@"Preferences will appear");
|
||||
[self.avatarsView autoSizeContent];
|
||||
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
if (subview.tag && ((UIControl *)subview).selected) {
|
||||
[self.avatarsView setContentOffset:CGPointMake( subview.center.x - self.avatarsView.bounds.size.width / 2, 0 )
|
||||
animated:animated];
|
||||
}
|
||||
} recurse:NO];
|
||||
inf( @"Preferences will appear" );
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
|
||||
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
|
||||
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:activeUser.defaultType];
|
||||
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%ld", (long)activeUser.avatar )];
|
||||
self.savePasswordSwitch.on = activeUser.saveKey;
|
||||
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Preferences"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
inf(@"Preferences will disappear");
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
MPCheckpoint( MPCheckpointLogs, @{
|
||||
@"trace" : [MPiOSConfig get].traceMode
|
||||
} );
|
||||
[self performSegueWithIdentifier:@"MP_Logs" sender:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotate {
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
|
||||
|
||||
return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
|
||||
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
|
||||
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
|
||||
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
|
||||
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
if (cell == self.exportCell)
|
||||
[[MPiOSAppDelegate get] export];
|
||||
|
||||
else if (cell == self.changeMPCell) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
|
||||
}];
|
||||
if (cell == self.signOutCell) {
|
||||
MPPasswordsViewController *passwordsVC = [self dismissPopup];
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
}
|
||||
if (cell == self.feedbackCell)
|
||||
[[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self];
|
||||
if (cell == self.exportCell)
|
||||
[[MPiOSAppDelegate get] showExportForVC:self];
|
||||
if (cell == self.coachmarksCell) {
|
||||
MPPasswordsViewController *passwordsVC = [self dismissPopup];
|
||||
passwordsVC.coachmark.coached = NO;
|
||||
[passwordsVC performSegueWithIdentifier:@"coachmarks" sender:self];
|
||||
}
|
||||
if (cell == self.checkInconsistencies)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
if ([[MPiOSAppDelegate get] findAndFixInconsistenciesSaveInContext:context] == MPFixableResultNoProblems)
|
||||
[PearlAlert showAlertWithTitle:@"No Inconsistencies" message:
|
||||
@"No inconsistencies were detected in your sites."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
||||
}];
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - MPTypeDelegate
|
||||
|
||||
- (void)didSelectType:(MPElementType)type {
|
||||
|
||||
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:type];
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||
activeUser.defaultType = type;
|
||||
[context saveToStore];
|
||||
}];
|
||||
}
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
return [[MPiOSAppDelegate get] activeUserForMainThread].defaultType;
|
||||
}
|
||||
|
||||
#pragma mark - IBActions
|
||||
|
||||
- (IBAction)didToggleSwitch:(UISwitch *)sender {
|
||||
- (IBAction)valueChanged:(id)sender {
|
||||
|
||||
if (sender == self.savePasswordSwitch)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
|
||||
if ((activeUser.saveKey = sender.on))
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||
if ((activeUser.saveKey = self.savePasswordSwitch.on))
|
||||
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
|
||||
else
|
||||
[[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser];
|
||||
[moc saveToStore];
|
||||
[context saveToStore];
|
||||
}];
|
||||
|
||||
if (sender == self.generatedTypeControl || sender == self.storedTypeControl) {
|
||||
if (sender == self.generatedTypeControl)
|
||||
self.storedTypeControl.selectedSegmentIndex = -1;
|
||||
else if (sender == self.storedTypeControl)
|
||||
self.generatedTypeControl.selectedSegmentIndex = -1;
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPElementType defaultType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = [self typeForSelectedSegment];
|
||||
[context saveToStore];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:defaultType];
|
||||
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)previousAvatar:(id)sender {
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||
activeUser.avatar = (activeUser.avatar - 1 + MPAvatarCount) % MPAvatarCount;
|
||||
[context saveToStore];
|
||||
|
||||
long avatar = activeUser.avatar;
|
||||
PearlMainQueue( ^{
|
||||
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%ld", avatar )];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)nextAvatar:(id)sender {
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||
activeUser.avatar = (activeUser.avatar + 1 + MPAvatarCount) % MPAvatarCount;
|
||||
[context saveToStore];
|
||||
|
||||
long avatar = activeUser.avatar;
|
||||
PearlMainQueue( ^{
|
||||
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%ld", avatar )];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (MPPasswordsViewController *)dismissPopup {
|
||||
|
||||
for (UIViewController *vc = self; (vc = vc.parentViewController);)
|
||||
if ([vc isKindOfClass:[MPPasswordsViewController class]]) {
|
||||
MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc;
|
||||
[passwordsVC dismissPopdown:self];
|
||||
return passwordsVC;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (enum MPElementType)typeForSelectedSegment {
|
||||
|
||||
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex;
|
||||
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
|
||||
|
||||
switch (selectedGeneratedIndex) {
|
||||
case 0:
|
||||
return MPElementTypeGeneratedMaximum;
|
||||
case 1:
|
||||
return MPElementTypeGeneratedLong;
|
||||
case 2:
|
||||
return MPElementTypeGeneratedMedium;
|
||||
case 3:
|
||||
return MPElementTypeGeneratedBasic;
|
||||
case 4:
|
||||
return MPElementTypeGeneratedShort;
|
||||
case 5:
|
||||
return MPElementTypeGeneratedPIN;
|
||||
default:
|
||||
|
||||
switch (selectedStoredIndex) {
|
||||
case 0:
|
||||
return MPElementTypeStoredPersonal;
|
||||
case 1:
|
||||
return MPElementTypeStoredDevicePrivate;
|
||||
default:
|
||||
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex,
|
||||
(long)selectedStoredIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)generatedSegmentIndexForType:(MPElementType)type {
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return 0;
|
||||
case MPElementTypeGeneratedLong:
|
||||
return 1;
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return 2;
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return 3;
|
||||
case MPElementTypeGeneratedShort:
|
||||
return 4;
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return 5;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)storedSegmentIndexForType:(MPElementType)type {
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeStoredPersonal:
|
||||
return 0;
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return 1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
23
MasterPassword/ObjC/iOS/MPPreferencesViewControllerOld.h
Normal file
23
MasterPassword/ObjC/iOS/MPPreferencesViewControllerOld.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// MPPreferencesViewController.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 04/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "MPTypeViewController.h"
|
||||
|
||||
@interface MPPreferencesViewControllerOld : UITableViewController<MPTypeDelegate>
|
||||
|
||||
@property(weak, nonatomic) IBOutlet UIScrollView *avatarsView;
|
||||
@property(weak, nonatomic) IBOutlet UIButton *avatarTemplate;
|
||||
@property(weak, nonatomic) IBOutlet UISwitch *savePasswordSwitch;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *changeMPCell;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *defaultTypeLabel;
|
||||
|
||||
- (IBAction)didToggleSwitch:(UISwitch *)sender;
|
||||
|
||||
@end
|
178
MasterPassword/ObjC/iOS/MPPreferencesViewControllerOld.m
Normal file
178
MasterPassword/ObjC/iOS/MPPreferencesViewControllerOld.m
Normal file
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// MPPreferencesViewController.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 04/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import "MPPreferencesViewControllerOld.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@interface MPPreferencesViewControllerOld()
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPPreferencesViewControllerOld
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.avatarTemplate.hidden = YES;
|
||||
|
||||
for (NSUInteger a = 0; a < MPAvatarCount; ++a) {
|
||||
UIButton *avatar = [self.avatarTemplate clone];
|
||||
avatar.tag = a;
|
||||
avatar.hidden = NO;
|
||||
avatar.center = CGPointMake(
|
||||
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
|
||||
self.avatarTemplate.center.y );
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%ld", (long)a )]
|
||||
forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
|
||||
|
||||
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
|
||||
avatar.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
avatar.layer.shadowOpacity = 1;
|
||||
avatar.layer.shadowRadius = 5;
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[avatar onHighlightOrSelect:^(BOOL highlighted, BOOL selected) {
|
||||
if (highlighted || selected)
|
||||
avatar.backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
else
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
} options:0];
|
||||
[avatar onSelect:^(BOOL selected) {
|
||||
if (selected) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
[[MPiOSAppDelegate get] activeUserInContext:moc].avatar = (unsigned)avatar.tag;
|
||||
[moc saveToStore];
|
||||
}];
|
||||
}
|
||||
} options:0];
|
||||
avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForMainThread].avatar);
|
||||
}
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
inf(@"Preferences will appear");
|
||||
[self.avatarsView autoSizeContent];
|
||||
[self.avatarsView enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
if (subview.tag && ((UIControl *)subview).selected) {
|
||||
[self.avatarsView setContentOffset:CGPointMake( subview.center.x - self.avatarsView.bounds.size.width / 2, 0 )
|
||||
animated:animated];
|
||||
}
|
||||
} recurse:NO];
|
||||
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
|
||||
self.savePasswordSwitch.on = activeUser.saveKey;
|
||||
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Preferences"];
|
||||
#endif
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
inf(@"Preferences will disappear");
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
|
||||
if (motion == UIEventSubtypeMotionShake) {
|
||||
MPCheckpoint( MPCheckpointLogs, @{
|
||||
@"trace" : [MPiOSConfig get].traceMode
|
||||
} );
|
||||
[self performSegueWithIdentifier:@"MP_Logs" sender:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotate {
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
|
||||
|
||||
return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([[segue identifier] isEqualToString:@"MP_ChooseType"])
|
||||
((MPTypeViewController *)[segue destinationViewController]).delegate = self;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
if (cell == self.exportCell)
|
||||
[[MPiOSAppDelegate get] showExportForVC:self];
|
||||
|
||||
else if (cell == self.changeMPCell) {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - MPTypeDelegate
|
||||
|
||||
- (void)didSelectType:(MPElementType)type {
|
||||
|
||||
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:type];
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||
activeUser.defaultType = type;
|
||||
[context saveToStore];
|
||||
}];
|
||||
}
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
return [[MPiOSAppDelegate get] activeUserForMainThread].defaultType;
|
||||
}
|
||||
|
||||
#pragma mark - IBActions
|
||||
|
||||
- (IBAction)didToggleSwitch:(UISwitch *)sender {
|
||||
|
||||
if (sender == self.savePasswordSwitch)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
|
||||
if ((activeUser.saveKey = sender.on))
|
||||
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
|
||||
else
|
||||
[[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser];
|
||||
[moc saveToStore];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
@@ -54,12 +54,4 @@
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)showGuide:(UIBarButtonItem *)sender {
|
||||
|
||||
[MPiOSConfig get].showSetup = @NO;
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
[[MPiOSAppDelegate get] showGuide];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -46,15 +46,15 @@
|
||||
[alert addSubview:alertAvatarScrollView];
|
||||
|
||||
CGPoint selectedOffset = CGPointZero;
|
||||
for (int a = 0; a < MPAvatarCount; ++a) {
|
||||
for (NSUInteger a = 0; a < MPAvatarCount; ++a) {
|
||||
UIButton *avatar = [self.avatarTemplate cloneAddedTo:alertAvatarScrollView];
|
||||
|
||||
avatar.tag = a;
|
||||
avatar.tag = (NSInteger)a;
|
||||
avatar.hidden = NO;
|
||||
avatar.center = CGPointMake(
|
||||
(20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
|
||||
20 + self.avatarTemplate.bounds.size.height / 2 );
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%d", a )] forState:UIControlStateNormal];
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%ld", (long)a )] forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
|
||||
|
||||
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
|
||||
@@ -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];
|
||||
}
|
||||
@@ -166,11 +162,11 @@
|
||||
self.wordList = wordListLines;
|
||||
|
||||
self.wordWall.alpha = 0;
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
[self.wordWall enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
[self initializeWordLabel:wordLabel];
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
@@ -276,12 +272,6 @@
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"MP_Settings"])
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
|
||||
return YES;
|
||||
@@ -292,6 +282,12 @@
|
||||
return UIStatusBarAnimationSlide;
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"MP_Settings"])
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
@@ -368,7 +364,7 @@
|
||||
avatar.layer.shadowRadius = 20;
|
||||
avatar.layer.masksToBounds = NO;
|
||||
avatar.backgroundColor = [UIColor clearColor];
|
||||
avatar.tag = user.avatar;
|
||||
avatar.tag = (NSInteger)user.avatar;
|
||||
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%lu", (unsigned long)user.avatar )]
|
||||
forState:UIControlStateNormal];
|
||||
@@ -420,8 +416,7 @@
|
||||
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
|
||||
|
||||
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
||||
inManagedObjectContext:context];
|
||||
MPUserEntity *newUser = [MPUserEntity insertNewObjectInContext:context];
|
||||
|
||||
[self showNewUserNameAlertFor:newUser saveInContext:context completion:^(BOOL finished) {
|
||||
newUserAvatar.selected = NO;
|
||||
@@ -590,7 +585,7 @@
|
||||
targetedUser = [self userForAvatar:targetedAvatar inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||
}
|
||||
|
||||
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
[self.avatarsView enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
if (![[self.avatarToUserOID allKeys] containsObject:[NSValue valueWithNonretainedObject:subview]])
|
||||
// This subview is not one of the user avatars.
|
||||
return;
|
||||
@@ -599,10 +594,10 @@
|
||||
BOOL isTargeted = avatar == targetedAvatar;
|
||||
|
||||
avatar.userInteractionEnabled = isTargeted;
|
||||
avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1: 0.4;
|
||||
avatar.alpha = isTargeted? 1: [self selectedUserForThread]? 0.1F: 0.4F;
|
||||
|
||||
[self updateAvatarShadowColor:avatar isTargeted:isTargeted];
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
|
||||
if (allowScroll) {
|
||||
CGPoint targetContentOffset = CGPointMake(
|
||||
@@ -614,10 +609,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 );
|
||||
@@ -625,22 +617,22 @@
|
||||
|
||||
- (void)beginWordWallAnimation {
|
||||
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
[self.wordWall enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
if (wordLabel.frame.origin.x < -self.wordWall.frame.size.width / 3) {
|
||||
wordLabel.frame = CGRectSetX( wordLabel.frame, wordLabel.frame.origin.x + self.wordWall.frame.size.width );
|
||||
[self initializeWordLabel:wordLabel];
|
||||
}
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
|
||||
if (self.wordWallAnimating)
|
||||
[UIView animateWithDuration:15 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
[self.wordWall enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
[self.wordWall enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||
UILabel *wordLabel = (UILabel *)subview;
|
||||
|
||||
wordLabel.frame = CGRectSetX( wordLabel.frame, wordLabel.frame.origin.x - self.wordWall.frame.size.width / 3 );
|
||||
} recurse:NO];
|
||||
} recurse:NO];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[self beginWordWallAnimation];
|
||||
@@ -649,7 +641,7 @@
|
||||
|
||||
- (void)initializeWordLabel:(UILabel *)wordLabel {
|
||||
|
||||
wordLabel.alpha = 0.05 + (random() % 35) / 100.0F;
|
||||
wordLabel.alpha = 0.05F + (random() % 35) / 100.0F;
|
||||
wordLabel.text = (self.wordList)[(NSUInteger)random() % [self.wordList count]];
|
||||
}
|
||||
|
||||
@@ -725,7 +717,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 +743,7 @@
|
||||
else
|
||||
[self avatarForUser:[self selectedUserForThread]].backgroundColor = self.avatarTemplate.backgroundColor;
|
||||
}];
|
||||
});
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)updateAvatarShadowColor:(UIButton *)avatar isTargeted:(BOOL)targeted {
|
||||
@@ -1132,9 +1124,9 @@
|
||||
|
||||
- (IBAction)google:(id)sender {
|
||||
|
||||
id<GPPShareBuilder> shareDialog = [[GPPShare sharedInstance] shareDialog];
|
||||
[[[shareDialog setURLToShare:[NSURL URLWithString:@"http://masterpasswordapp.com"]]
|
||||
setPrefillText:@"I've started doing passwords properly thanks to Master Password."] open];
|
||||
// id<GPPShareBuilder> shareDialog = [[GPPShare sharedInstance] shareDialog];
|
||||
// [[[shareDialog setURLToShare:[NSURL URLWithString:@"http://masterpasswordapp.com"]]
|
||||
// setPrefillText:@"I've started doing passwords properly thanks to Master Password."] open];
|
||||
}
|
||||
|
||||
- (IBAction)mail:(id)sender {
|
||||
@@ -1186,7 +1178,7 @@
|
||||
}
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex] + 3) {
|
||||
// Mailing List
|
||||
[PearlEMail sendEMailTo:@"masterpassword-join@lists.lyndir.com" subject:@"Subscribe"
|
||||
[PearlEMail sendEMailTo:@"masterpassword-join@lists.lyndir.com" fromVC:self subject:@"Subscribe"
|
||||
body:@"Press 'Send' now to subscribe to the Master Password mailing list.\n\n"
|
||||
@"You'll be kept up-to-date on the evolution of and discussions revolving Master Password."];
|
||||
return;
|
||||
|
46
MasterPassword/ObjC/iOS/MPUsersViewController.h
Normal file
46
MasterPassword/ObjC/iOS/MPUsersViewController.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLGitTip.h"
|
||||
|
||||
@interface MPUsersViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITextFieldDelegate>
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
|
||||
@property(weak, nonatomic) IBOutlet UIView *userSelectionContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIButton *marqueeButton;
|
||||
@property(weak, nonatomic) IBOutlet UIView *gitTipTip;
|
||||
@property(weak, nonatomic) IBOutlet LLGitTip *gitTipButton;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *entryField;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *entryTipTitleLabel;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *entryTipSubtitleLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIView *entryTipContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIView *footerContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *storeLoadingActivity;
|
||||
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
|
||||
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
|
||||
@property (strong, nonatomic) IBOutlet UIButton *nextAvatarButton;
|
||||
@property (strong, nonatomic) IBOutlet UIButton *previousAvatarButton;
|
||||
|
||||
@property(assign, nonatomic) BOOL active;
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated;
|
||||
- (IBAction)changeAvatar:(UIButton *)sender;
|
||||
|
||||
@end
|
833
MasterPassword/ObjC/iOS/MPUsersViewController.m
Normal file
833
MasterPassword/ObjC/iOS/MPUsersViewController.m
Normal file
@@ -0,0 +1,833 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPUsersViewController.h"
|
||||
#import "MPEntities.h"
|
||||
#import "MPAvatarCell.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "PearlSizedTextView.h"
|
||||
#import "MPWebViewController.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
/** The users are all inactive */
|
||||
MPActiveUserStateNone,
|
||||
/** The selected user is activated and being logged in with */
|
||||
MPActiveUserStateLogin,
|
||||
/** The selected user is activated and its user name is being asked for */
|
||||
MPActiveUserStateUserName,
|
||||
/** The selected user is activated and its new master password is being asked for */
|
||||
MPActiveUserStateMasterPasswordChoice,
|
||||
/** The selected user is activated and the confirmation of the previously entered master password is being asked for */
|
||||
MPActiveUserStateMasterPasswordConfirmation,
|
||||
/** The selected user is activated displayed at the top with the rest of the UI inactive */
|
||||
MPActiveUserStateMinimized,
|
||||
};
|
||||
|
||||
@interface MPUsersViewController()
|
||||
|
||||
@property(nonatomic) MPActiveUserState activeUserState;
|
||||
@property(nonatomic, strong) NSArray *userIDs;
|
||||
@property(nonatomic, strong) NSTimer *marqueeTipTimer;
|
||||
@property(nonatomic, strong) NSArray *marqueeTipTexts;
|
||||
@property(nonatomic) NSUInteger marqueeTipTextIndex;
|
||||
@end
|
||||
|
||||
@implementation MPUsersViewController {
|
||||
__weak id _storeObserver;
|
||||
__weak id _mocObserver;
|
||||
NSArray *_notificationObservers;
|
||||
NSString *_masterPasswordChoice;
|
||||
NSOperationQueue *_afterUpdates;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
_afterUpdates = [NSOperationQueue new];
|
||||
|
||||
self.marqueeTipTexts = @[
|
||||
strl( @"Thanks, lhunath ➚" ),
|
||||
strl( @"Press and hold to delete or reset user." ),
|
||||
strl( @"Shake for emergency generator." ),
|
||||
];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
self.avatarCollectionView.allowsMultipleSelection = YES;
|
||||
[self.entryField addTarget:self action:@selector(textFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged];
|
||||
|
||||
[self setActive:YES animated:NO];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.userSelectionContainer.alpha = 0;
|
||||
|
||||
[self observeStore];
|
||||
[self registerObservers];
|
||||
[self reloadUsers];
|
||||
|
||||
[self.marqueeTipTimer invalidate];
|
||||
self.marqueeTipTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(firedMarqueeTimer:)
|
||||
userInfo:nil repeats:YES];
|
||||
[self firedMarqueeTimer:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self stopObservingStore];
|
||||
|
||||
[self.marqueeTipTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
[self.avatarCollectionView.collectionViewLayout invalidateLayout];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"web"])
|
||||
((MPWebViewController *)segue.destinationViewController).initialURL = [NSURL URLWithString:@"http://thanks.lhunath.com"];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
if (textField == self.entryField) {
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
[textField resignFirstResponder];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin: {
|
||||
[self.entryField endEditing:YES];
|
||||
[self selectedAvatar].spinnerActive = YES;
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL signedIn = NO, isNew = NO;
|
||||
MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew];
|
||||
if (!isNew && user)
|
||||
signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context
|
||||
usingMasterPassword:self.entryField.text];
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
self.entryField.text = @"";
|
||||
[self selectedAvatar].spinnerActive = NO;
|
||||
|
||||
if (!signedIn) {
|
||||
// Sign in failed.
|
||||
[self showEntryTip:strl( @"Looks like a typo!\nTry again; that password was incorrect." )];
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateUserName: {
|
||||
NSString *userName = self.entryField.text;
|
||||
if (![userName length]) {
|
||||
// No name entered.
|
||||
[self showEntryTip:strl( @"First, enter your name." )];
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self selectedAvatar].name = userName;
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice: {
|
||||
NSString *masterPassword = self.entryField.text;
|
||||
if (![masterPassword length]) {
|
||||
// No password entered.
|
||||
[self showEntryTip:strl( @"Pick a master password." )];
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordConfirmation;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
NSString *masterPassword = self.entryField.text;
|
||||
if (![masterPassword length]) {
|
||||
// No password entered.
|
||||
[self showEntryTip:strl( @"Confirm your master password." )];
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![masterPassword isEqualToString:_masterPasswordChoice]) {
|
||||
// Master password confirmation failed.
|
||||
[self showEntryTip:strl( @"Looks like a typo!\nTry again; enter your master password twice." )];
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.entryField endEditing:YES];
|
||||
MPAvatarCell *avatarCell = [self selectedAvatar];
|
||||
avatarCell.spinnerActive = YES;
|
||||
if (![MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForAvatar:avatarCell inContext:context isNew:&isNew];
|
||||
if (isNew) {
|
||||
user = [MPUserEntity insertNewObjectInContext:context];
|
||||
user.avatar = avatarCell.avatar;
|
||||
user.name = avatarCell.name;
|
||||
}
|
||||
|
||||
BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context usingMasterPassword:masterPassword];
|
||||
PearlMainQueue( ^{
|
||||
self.entryField.text = @"";
|
||||
[self selectedAvatar].spinnerActive = NO;
|
||||
|
||||
if (!signedIn) {
|
||||
// Sign in failed, shouldn't happen for a new user.
|
||||
[self showEntryTip:strl( @"Couldn't create new user." )];
|
||||
self.activeUserState = MPActiveUserStateNone;
|
||||
return;
|
||||
}
|
||||
} );
|
||||
}])
|
||||
avatarCell.spinnerActive = NO;
|
||||
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized: {
|
||||
[textField resignFirstResponder];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification.
|
||||
- (void)textFieldEditingChanged:(UITextField *)textField {
|
||||
|
||||
if (textField == self.entryField) {
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone:
|
||||
break;
|
||||
case MPActiveUserStateLogin:
|
||||
break;
|
||||
case MPActiveUserStateUserName: {
|
||||
NSString *userName = self.entryField.text;
|
||||
[self selectedAvatar].name = [userName length]? userName: strl( @"New User" );
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
break;
|
||||
case MPActiveUserStateMasterPasswordConfirmation:
|
||||
break;
|
||||
case MPActiveUserStateMinimized:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegateFlowLayout
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
CGSize parentSize = self.avatarCollectionView.bounds.size;
|
||||
return CGSizeMake( parentSize.width / 2, parentSize.height );
|
||||
}
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
if (collectionView == self.avatarCollectionView)
|
||||
return [self.userIDs count] + 1;
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
MPAvatarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPAvatarCell reuseIdentifier] forIndexPath:indexPath];
|
||||
[cell addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPress:)]];
|
||||
[self updateModeForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
[self updateVisibilityForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]
|
||||
isNew:&isNew];
|
||||
if (isNew)
|
||||
// New User
|
||||
cell.avatar = MPAvatarAdd;
|
||||
else {
|
||||
// Existing User
|
||||
cell.avatar = user.avatar;
|
||||
cell.name = user.name;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
[self.avatarCollectionView scrollToItemAtIndexPath:indexPath
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
|
||||
|
||||
// Deselect all other cells.
|
||||
for (NSUInteger otherItem = 0; otherItem < [collectionView numberOfItemsInSection:indexPath.section]; ++otherItem)
|
||||
if (otherItem != indexPath.item) {
|
||||
NSIndexPath *otherIndexPath = [NSIndexPath indexPathForItem:otherItem inSection:indexPath.section];
|
||||
[collectionView deselectItemAtIndexPath:otherIndexPath animated:YES];
|
||||
}
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]
|
||||
isNew:&isNew];
|
||||
|
||||
if (isNew)
|
||||
self.activeUserState = MPActiveUserStateUserName;
|
||||
else if (!user.keyID)
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
else
|
||||
self.activeUserState = MPActiveUserStateLogin;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView)
|
||||
self.activeUserState = MPActiveUserStateNone;
|
||||
}
|
||||
|
||||
#pragma mark - UILongPressGestureRecognizer
|
||||
|
||||
- (void)didLongPress:(UILongPressGestureRecognizer *)recognizer {
|
||||
|
||||
if ([recognizer.view isKindOfClass:[MPAvatarCell class]]) {
|
||||
if (recognizer.state != UIGestureRecognizerStateBegan)
|
||||
// Don't show the action menu unless the state is Began.
|
||||
return;
|
||||
|
||||
MPAvatarCell *avatarCell = (MPAvatarCell *)recognizer.view;
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForAvatar:avatarCell inContext:mainContext isNew:&isNew];
|
||||
NSManagedObjectID *userID = user.objectID;
|
||||
if (isNew || !user)
|
||||
return;
|
||||
|
||||
[PearlSheet showSheetWithTitle:user.name
|
||||
viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
// Delete User
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||
if (!user_)
|
||||
return;
|
||||
|
||||
[context deleteObject:user_];
|
||||
[context saveToStore];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
// Reset Password
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||
if (!user_)
|
||||
return;
|
||||
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
|
||||
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
|
||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
|
||||
targetContentOffset:(inout CGPoint *)targetContentOffset {
|
||||
|
||||
if (scrollView == self.avatarCollectionView) {
|
||||
CGPoint offsetToCenter = self.avatarCollectionView.center;
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForItemAtPoint:
|
||||
CGPointPlusCGPoint( *targetContentOffset, offsetToCenter )];
|
||||
CGPoint targetCenter = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:avatarIndexPath].center;
|
||||
*targetContentOffset = CGPointMinusCGPoint( targetCenter, offsetToCenter );
|
||||
NSAssert([self.avatarCollectionView indexPathForItemAtPoint:targetCenter].item == avatarIndexPath.item, @"should be same item");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)showEntryTip:(NSString *)message {
|
||||
|
||||
NSUInteger newlineIndex = [message rangeOfString:@"\n"].location;
|
||||
NSString *messageTitle = newlineIndex == NSNotFound? message: [message substringToIndex:newlineIndex];
|
||||
NSString *messageSubtitle = newlineIndex == NSNotFound? nil: [message substringFromIndex:newlineIndex];
|
||||
self.entryTipTitleLabel.text = messageTitle;
|
||||
self.entryTipSubtitleLabel.text = messageSubtitle;
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.entryTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
PearlMainQueueAfter( 4, ^{
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.entryTipContainer.alpha = 0;
|
||||
}];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)firedMarqueeTimer:(NSTimer *)timer {
|
||||
|
||||
NSString *nextMarqueeString = self.marqueeTipTexts[self.marqueeTipTextIndex++ % [self.marqueeTipTexts count]];
|
||||
if ([nextMarqueeString isEqualToString:[self.marqueeButton titleForState:UIControlStateNormal]])
|
||||
return;
|
||||
|
||||
[UIView animateWithDuration:timer? 0.5: 0 animations:^{
|
||||
self.marqueeButton.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
if (!finished)
|
||||
return;
|
||||
|
||||
[self.marqueeButton setTitle:nextMarqueeString forState:UIControlStateNormal];
|
||||
[UIView animateWithDuration:timer? 0.5: 0 animations:^{
|
||||
self.marqueeButton.alpha = 0.5;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (MPAvatarCell *)selectedAvatar {
|
||||
|
||||
NSArray *selectedIndexPaths = self.avatarCollectionView.indexPathsForSelectedItems;
|
||||
if (![selectedIndexPaths count]) {
|
||||
// No selected user.
|
||||
return nil;
|
||||
}
|
||||
|
||||
return (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:selectedIndexPaths.firstObject];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)selectedUserInContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
MPAvatarCell *selectedAvatar = [self selectedAvatar];
|
||||
if (!selectedAvatar) {
|
||||
// No selected user.
|
||||
*isNew = NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self userForAvatar:selectedAvatar inContext:context isNew:isNew];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForAvatar:(MPAvatarCell *)cell inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
return [self userForIndexPath:[self.avatarCollectionView indexPathForCell:cell] inContext:context isNew:isNew];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForIndexPath:(NSIndexPath *)indexPath inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
if ((*isNew = indexPath.item >= [self.userIDs count]))
|
||||
return nil;
|
||||
|
||||
return [MPUserEntity existingObjectWithID:self.userIDs[indexPath.item] inContext:context];
|
||||
}
|
||||
|
||||
- (void)updateAvatars {
|
||||
|
||||
self.previousAvatarButton.alpha = 0;
|
||||
self.nextAvatarButton.alpha = 0;
|
||||
for (NSIndexPath *indexPath in self.avatarCollectionView.indexPathsForVisibleItems)
|
||||
[self updateAvatarAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)updateAvatarAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
|
||||
[self updateModeForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
[self updateVisibilityForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
}
|
||||
|
||||
- (void)updateModeForAvatar:(MPAvatarCell *)avatarCell atIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
|
||||
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
[self.avatarCollectionView deselectItemAtIndexPath:indexPath animated:YES];
|
||||
[avatarCell setMode:MPAvatarModeLowered animated:animated];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin:
|
||||
case MPActiveUserStateUserName:
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
if ([self.avatarCollectionView.indexPathsForSelectedItems containsObject:indexPath])
|
||||
[avatarCell setMode:MPAvatarModeRaisedAndActive animated:animated];
|
||||
else
|
||||
[avatarCell setMode:MPAvatarModeRaisedButInactive animated:animated];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized: {
|
||||
if ([self.avatarCollectionView.indexPathsForSelectedItems containsObject:indexPath])
|
||||
[avatarCell setMode:MPAvatarModeRaisedAndMinimized animated:animated];
|
||||
else
|
||||
[avatarCell setMode:MPAvatarModeRaisedAndHidden animated:animated];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateVisibilityForAvatar:(MPAvatarCell *)cell atIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
|
||||
|
||||
CGFloat current = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:indexPath].center.x -
|
||||
self.avatarCollectionView.contentOffset.x;
|
||||
CGFloat max = self.avatarCollectionView.bounds.size.width;
|
||||
|
||||
CGFloat visibility = MAX(0, MIN( 1, 1 - ABS( current / (max / 2) - 1 ) ));
|
||||
[cell setVisibility:visibility animated:animated];
|
||||
|
||||
if (cell.newUser) {
|
||||
self.previousAvatarButton.alpha = cell.mode == MPAvatarModeRaisedAndActive? visibility * 0.7f: 0;
|
||||
self.nextAvatarButton.alpha = cell.mode == MPAvatarModeRaisedAndActive? visibility * 0.7f: 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)afterUpdatesMainQueue:(void (^)(void))block {
|
||||
|
||||
[_afterUpdates addOperationWithBlock:^{
|
||||
PearlMainQueue( block );
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
if ([_notificationObservers count])
|
||||
return;
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
// [self emergencyCloseAnimated:NO];
|
||||
self.userSelectionContainer.alpha = 0;
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self reloadUsers];
|
||||
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.userSelectionContainer.alpha = 1;
|
||||
}];
|
||||
}],
|
||||
];
|
||||
|
||||
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
||||
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
|
||||
[_self updateAvatars];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
|
||||
for (id observer in _notificationObservers)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
_notificationObservers = nil;
|
||||
|
||||
[self removeKeyPathObservers];
|
||||
}
|
||||
|
||||
- (void)observeStore {
|
||||
|
||||
Weakify(self);
|
||||
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.avatarCollectionView.alpha = mainContext? 1: 0;
|
||||
}];
|
||||
if (mainContext && self.storeLoadingActivity.isAnimating)
|
||||
[self.storeLoadingActivity stopAnimating];
|
||||
if (!mainContext && !self.storeLoadingActivity.isAnimating)
|
||||
[self.storeLoadingActivity startAnimating];
|
||||
|
||||
if (!_mocObserver && mainContext)
|
||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
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]
|
||||
addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
[self reloadUsers];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopObservingStore {
|
||||
|
||||
if (_mocObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
||||
if (_storeObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
|
||||
}
|
||||
|
||||
- (void)reloadUsers {
|
||||
|
||||
[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;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setActive:(BOOL)active {
|
||||
|
||||
[self setActive:active animated:NO];
|
||||
}
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated {
|
||||
|
||||
_active = active;
|
||||
dbg(@"active -> %d", active);
|
||||
|
||||
if (active)
|
||||
[self setActiveUserState:MPActiveUserStateNone animated:animated];
|
||||
else
|
||||
[self setActiveUserState:MPActiveUserStateMinimized animated:animated];
|
||||
}
|
||||
|
||||
- (void)setUserIDs:(NSArray *)userIDs {
|
||||
|
||||
_userIDs = userIDs;
|
||||
dbg(@"userIDs -> %lu", (unsigned long)[userIDs count]);
|
||||
|
||||
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 {
|
||||
|
||||
[self setActiveUserState:activeUserState animated:YES];
|
||||
}
|
||||
|
||||
- (void)setActiveUserState:(MPActiveUserState)activeUserState animated:(BOOL)animated {
|
||||
|
||||
_activeUserState = activeUserState;
|
||||
_masterPasswordChoice = nil;
|
||||
|
||||
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.4f: 0 animations:^{
|
||||
MPAvatarCell *selectedAvatar = [self selectedAvatar];
|
||||
|
||||
// Set avatar modes.
|
||||
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:animated];
|
||||
[self updateVisibilityForAvatar:avatarCell atIndexPath:indexPath animated:animated];
|
||||
|
||||
if (selectedAvatar && avatarCell == selectedAvatar)
|
||||
[self.avatarCollectionView scrollToItemAtIndexPath:indexPath
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
|
||||
}
|
||||
|
||||
// Set the entry container's contents.
|
||||
switch (activeUserState) {
|
||||
case MPActiveUserStateNone:
|
||||
dbg(@"activeUserState -> none");
|
||||
break;
|
||||
case MPActiveUserStateLogin: {
|
||||
dbg(@"activeUserState -> login");
|
||||
self.entryLabel.text = strl( @"Enter your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
self.entryField.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateUserName: {
|
||||
dbg(@"activeUserState -> userName");
|
||||
self.entryLabel.text = strl( @"Enter your full name:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = NO;
|
||||
self.entryField.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice: {
|
||||
dbg(@"activeUserState -> masterPasswordChoice");
|
||||
self.entryLabel.text = strl( @"Choose your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
self.entryField.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
dbg(@"activeUserState -> masterPasswordConfirmation");
|
||||
_masterPasswordChoice = self.entryField.text;
|
||||
self.entryLabel.text = strl( @"Confirm your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
self.entryField.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized:
|
||||
dbg(@"activeUserState -> minimized");
|
||||
break;
|
||||
}
|
||||
|
||||
// Manage the entry container depending on whether a user is activate or not.
|
||||
switch (activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
self.navigationBarToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarCollectionView.scrollEnabled = YES;
|
||||
self.entryContainer.alpha = 0;
|
||||
self.footerContainer.alpha = 1;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin:
|
||||
case MPActiveUserStateUserName:
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
self.navigationBarToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarCollectionView.scrollEnabled = NO;
|
||||
self.entryContainer.alpha = 1;
|
||||
self.footerContainer.alpha = 1;
|
||||
requestFirstResponder = YES;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized: {
|
||||
self.navigationBarToTopConstraint.priority = 1;
|
||||
self.avatarCollectionView.scrollEnabled = NO;
|
||||
self.entryContainer.alpha = 0;
|
||||
self.footerContainer.alpha = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
[self.navigationBarToTopConstraint apply];
|
||||
} completion:^(BOOL finished) {
|
||||
dbg(@"resume updates");
|
||||
[_afterUpdates setSuspended:NO];
|
||||
}];
|
||||
|
||||
UIResponder *oldFirstResponder = [UIResponder findFirstResponder];
|
||||
if (requestFirstResponder)
|
||||
[self.entryField becomeFirstResponder];
|
||||
else
|
||||
[self.entryField resignFirstResponder];
|
||||
UIResponder *newFirstResponder = [UIResponder findFirstResponder];
|
||||
if (newFirstResponder != oldFirstResponder)
|
||||
dbg(@"first responder: %@ -> %@", oldFirstResponder, newFirstResponder);
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)changeAvatar:(UIButton *)sender {
|
||||
|
||||
if (sender == self.previousAvatarButton)
|
||||
--[self selectedAvatar].avatar;
|
||||
if (sender == self.nextAvatarButton)
|
||||
++[self selectedAvatar].avatar;
|
||||
}
|
||||
|
||||
@end
|
29
MasterPassword/ObjC/iOS/MPWebViewController.h
Normal file
29
MasterPassword/ObjC/iOS/MPWebViewController.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPWebViewController.h
|
||||
// MPWebViewController
|
||||
//
|
||||
// Created by lhunath on 2014-05-09.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface MPWebViewController : UIViewController <UIWebViewDelegate>
|
||||
|
||||
@property(nonatomic) IBOutlet UIWebView *webView;
|
||||
@property(nonatomic) IBOutlet UINavigationItem *webNavigationItem;
|
||||
|
||||
@property(nonatomic) NSURL *initialURL;
|
||||
|
||||
@end
|
85
MasterPassword/ObjC/iOS/MPWebViewController.m
Normal file
85
MasterPassword/ObjC/iOS/MPWebViewController.m
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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 <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPWebViewController.h
|
||||
// MPWebViewController
|
||||
//
|
||||
// Created by lhunath on 2014-05-09.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPWebViewController.h"
|
||||
|
||||
@implementation MPWebViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
[self.view adjustContentInsets];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
if (!self.initialURL)
|
||||
self.initialURL = [NSURL URLWithString:@"http://masterpasswordapp.com"];
|
||||
|
||||
self.webView.alpha = 0;
|
||||
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.initialURL]];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
#pragma mark - UIWebViewDelegate
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||
navigationType:(UIWebViewNavigationType)navigationType {
|
||||
|
||||
if ([request.URL isEqual:request.mainDocumentURL]) {
|
||||
self.webNavigationItem.title = request.URL.host;
|
||||
self.webNavigationItem.prompt = strl( @"Loading" );
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)webViewDidStartLoad:(UIWebView *)webView {
|
||||
|
||||
UIActivityIndicatorView *activityView =
|
||||
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||
[self.webNavigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc] initWithCustomView:activityView]];
|
||||
[activityView startAnimating];
|
||||
}
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
self.webView.alpha = 1;
|
||||
}];
|
||||
|
||||
[self.webNavigationItem setLeftBarButtonItem:[webView canGoBack]? [[UIBarButtonItem alloc]
|
||||
initWithTitle:@"⬅︎" style:UIBarButtonItemStylePlain target:webView action:@selector( goBack )]: nil];
|
||||
self.webNavigationItem.prompt = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)done:(id)sender {
|
||||
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
@@ -10,16 +10,13 @@
|
||||
#import <MessageUI/MessageUI.h>
|
||||
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import <GooglePlus/GooglePlus.h>
|
||||
|
||||
@interface MPiOSAppDelegate : MPAppDelegate_Shared
|
||||
|
||||
- (void)showGuide;
|
||||
- (void)showSetup;
|
||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
|
||||
|
||||
- (void)export;
|
||||
- (void)showExportForVC:(UIViewController *)viewController;
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset;
|
||||
|
||||
@end
|
||||
|
@@ -13,21 +13,24 @@
|
||||
|
||||
@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 *storeLoadingOverlay;
|
||||
@end
|
||||
|
||||
@implementation MPiOSAppDelegate
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||
if ([self class] == [MPiOSAppDelegate class]) {
|
||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].printLevel = PearlLogLevelDebug;
|
||||
[PearlLogger get].printLevel = PearlLogLevelDebug;
|
||||
#else
|
||||
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
@@ -59,11 +62,6 @@
|
||||
TESTFLIGHT_SDK_VERSION, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
|
||||
}
|
||||
#endif
|
||||
NSString *googlePlusClientID = [self googlePlusClientID];
|
||||
if ([googlePlusClientID length]) {
|
||||
inf(@"Initializing Google+");
|
||||
[[GPPSignIn sharedInstance] setClientID:googlePlusClientID];
|
||||
}
|
||||
#ifdef CRASHLYTICS
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
@@ -113,78 +111,20 @@
|
||||
#endif
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Analytics Setup: %@", exception);
|
||||
}
|
||||
@try {
|
||||
if (floor( NSFoundationVersionNumber ) <= NSFoundationVersionNumber_iOS_6_1) {
|
||||
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];
|
||||
[[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]
|
||||
}];
|
||||
|
||||
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake( 0, 5, 0, 5 )];
|
||||
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake( 0, 13, 0, 5 )];
|
||||
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UIBarButtonItem appearance]
|
||||
setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UIBarButtonItem appearance]
|
||||
setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[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 )]//,
|
||||
// Causes a bug in iOS where image views get oddly stretched... or something.
|
||||
//UITextAttributeFont: [UIFont fontWithName:@"HelveticaNeue" size:13.0f]
|
||||
}
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"]
|
||||
resizableImageWithCapInsets:UIEdgeInsetsMake( 25, 5, 5, 5 )];
|
||||
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
|
||||
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
|
||||
|
||||
// UIImage *minImage = [[UIImage imageNamed:@"slider-minimum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
// UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
// UIImage *thumbImage = [UIImage imageNamed:@"slider-handle"];
|
||||
//
|
||||
// [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
// [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
// [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
//
|
||||
// UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
// UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
// UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns"];
|
||||
// UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel"];
|
||||
// UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns"];
|
||||
//
|
||||
// [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
// [[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
//
|
||||
// [[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
// [[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
// [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Theme Setup: %@", exception);
|
||||
err( @"During Analytics Setup: %@", exception );
|
||||
}
|
||||
@try {
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self checkConfig];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
|
||||
object:note userInfo:nil];
|
||||
[self updateFromConfig];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note userInfo:nil];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note userInfo:nil];
|
||||
}];
|
||||
|
||||
#ifdef ADHOC
|
||||
@@ -200,20 +140,43 @@
|
||||
#endif
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Config Test: %@", exception);
|
||||
err( @"During Config Test: %@", exception );
|
||||
}
|
||||
@try {
|
||||
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Pearl Application Launch: %@", exception);
|
||||
err( @"During Pearl Application Launch: %@", exception );
|
||||
}
|
||||
@try {
|
||||
inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]);
|
||||
inf( @"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier] );
|
||||
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPFoundInconsistenciesNotification object:nil queue:nil usingBlock:
|
||||
^(NSNotification *note) {
|
||||
switch ((MPFixableResult)[note.userInfo[MPInconsistenciesFixResultUserKey] unsignedIntegerValue]) {
|
||||
|
||||
case MPFixableResultNoProblems:
|
||||
break;
|
||||
case MPFixableResultProblemsFixed:
|
||||
[PearlAlert showAlertWithTitle:@"Inconsistencies Fixed" message:
|
||||
@"Some inconsistencies were detected in your sites.\n"
|
||||
@"All issues were fixed."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
||||
break;
|
||||
case MPFixableResultProblemsNotFixed:
|
||||
[PearlAlert showAlertWithTitle:@"Inconsistencies Found" message:
|
||||
@"Some inconsistencies were detected in your sites.\n"
|
||||
@"Not all issues could be fixed. Try signing in to each user or checking the logs."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
||||
break;
|
||||
}
|
||||
}];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
if ([[MPiOSConfig get].showSetup boolValue])
|
||||
[[MPiOSAppDelegate get] showSetup];
|
||||
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
|
||||
} );
|
||||
|
||||
MPCheckpoint( MPCheckpointStarted, @{
|
||||
@@ -229,7 +192,7 @@
|
||||
} );
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"During Post-Startup: %@", exception);
|
||||
err( @"During Post-Startup: %@", exception );
|
||||
}
|
||||
|
||||
return YES;
|
||||
@@ -242,10 +205,6 @@
|
||||
if (!url)
|
||||
return NO;
|
||||
|
||||
// Google+
|
||||
if ([[GPPSignIn sharedInstance] handleURL:url sourceApplication:sourceApplication annotation:annotation])
|
||||
return YES;
|
||||
|
||||
// Arbitrary URL to mpsites data.
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSError *error;
|
||||
@@ -253,11 +212,11 @@
|
||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||
returningResponse:&response error:&error];
|
||||
if (error)
|
||||
err(@"While reading imported sites from %@: %@", url, error);
|
||||
err( @"While reading imported sites from %@: %@", url, error );
|
||||
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) {
|
||||
@@ -294,7 +253,7 @@
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[PearlAlert showAlertWithTitle:PearlString( @"Master Password for\n%@", userName )
|
||||
message:PearlString( @"Imports %lu sites, overwriting %lu.",
|
||||
(unsigned long)importCount, (unsigned long)deleteCount )
|
||||
(unsigned long)importCount, (unsigned long)deleteCount )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
@try {
|
||||
@@ -337,7 +296,7 @@
|
||||
|
||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
||||
|
||||
inf(@"Received memory warning.");
|
||||
inf( @"Received memory warning." );
|
||||
|
||||
[super applicationDidReceiveMemoryWarning:application];
|
||||
}
|
||||
@@ -374,7 +333,7 @@
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Will deactivate");
|
||||
inf( @"Will deactivate" );
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOutAnimated:NO];
|
||||
|
||||
@@ -388,9 +347,8 @@
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Re-activated");
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
|
||||
object:application userInfo:nil];
|
||||
inf( @"Re-activated" );
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:application];
|
||||
|
||||
#ifdef LOCALYTICS
|
||||
[[LocalyticsSession sharedLocalyticsSession] resume];
|
||||
@@ -402,25 +360,11 @@
|
||||
|
||||
#pragma mark - Behavior
|
||||
|
||||
- (void)showGuide {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||
|
||||
MPCheckpoint( MPCheckpointShowGuide, nil );
|
||||
}
|
||||
|
||||
- (void)showSetup {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Setup" sender:self];
|
||||
|
||||
MPCheckpoint( MPCheckpointShowSetup, nil );
|
||||
}
|
||||
|
||||
- (void)showReview {
|
||||
|
||||
MPCheckpoint( MPCheckpointReview, nil );
|
||||
|
||||
[super showReview];
|
||||
|
||||
MPCheckpoint( MPCheckpointReview, nil );
|
||||
}
|
||||
|
||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
|
||||
@@ -458,14 +402,14 @@
|
||||
|
||||
[[[PearlEMail alloc] initForEMailTo:@"Master Password Development <masterpassword@lyndir.com>"
|
||||
subject:PearlString( @"Feedback for Master Password [%@]",
|
||||
[[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"] )
|
||||
[[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"] )
|
||||
body:PearlString( @"\n\n\n"
|
||||
@"--\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]
|
||||
@@ -479,32 +423,32 @@
|
||||
showComposerForVC:viewController];
|
||||
}
|
||||
|
||||
- (void)export {
|
||||
- (void)showExportForVC:(UIViewController *)viewController {
|
||||
|
||||
[PearlAlert showNotice:
|
||||
@"This will export all your site names.\n\n"
|
||||
@"You can open the export with a text editor to get an overview of all your sites.\n\n"
|
||||
@"The file also acts as a personal backup of your site list in case you don't sync with iCloud/iTunes."
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
|
||||
@"Would you like to make all your passwords visible in the export?\n\n"
|
||||
@"A safe export will only include your stored passwords, in an encrypted manner, "
|
||||
@"making the result safe from falling in the wrong hands.\n\n"
|
||||
@"If all your passwords are shown and somebody else finds the export, "
|
||||
@"they could gain access to all your sites!"
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
|
||||
// Safe Export
|
||||
[self exportShowPasswords:NO];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
// Show Passwords
|
||||
[self exportShowPasswords:YES];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
[PearlAlert showAlertWithTitle:@"Reveal Passwords?" message:
|
||||
@"Would you like to make all your passwords visible in the export?\n\n"
|
||||
@"A safe export will only include your stored passwords, in an encrypted manner, "
|
||||
@"making the result safe from falling in the wrong hands.\n\n"
|
||||
@"If all your passwords are shown and somebody else finds the export, "
|
||||
@"they could gain access to all your sites!"
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 0)
|
||||
// Safe Export
|
||||
[self showExportRevealPasswords:NO forVC:viewController];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
// Show Passwords
|
||||
[self showExportRevealPasswords:YES forVC:viewController];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil];
|
||||
} otherTitles:nil];
|
||||
}
|
||||
|
||||
- (void)exportShowPasswords:(BOOL)showPasswords {
|
||||
- (void)showExportRevealPasswords:(BOOL)revealPasswords forVC:(UIViewController *)viewController {
|
||||
|
||||
if (![PearlEMail canSendMail]) {
|
||||
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
|
||||
@@ -517,10 +461,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords];
|
||||
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
|
||||
NSString *message;
|
||||
|
||||
if (showPasswords)
|
||||
if (revealPasswords)
|
||||
message = PearlString( @"Export of Master Password sites with passwords included.\n\n"
|
||||
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
|
||||
@"--\n"
|
||||
@@ -541,7 +485,7 @@
|
||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
||||
|
||||
[PearlEMail sendEMailTo:nil subject:@"Master Password Export" body:message
|
||||
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
||||
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain" fileName:
|
||||
PearlString( @"%@ (%@).mpsites", [self activeUserForMainThread].name,
|
||||
@@ -549,7 +493,7 @@
|
||||
nil];
|
||||
}
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset {
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
|
||||
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@@ -562,7 +506,7 @@
|
||||
return;
|
||||
|
||||
[moc performBlockAndWait:^{
|
||||
inf(@"Unsetting master password for: %@.", user.userID);
|
||||
inf( @"Unsetting master password for: %@.", user.userID );
|
||||
user.keyID = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
[moc saveToStore];
|
||||
@@ -578,16 +522,14 @@
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - PearlConfigDelegate
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
|
||||
object:NSStringFromSelector( configKey ) userInfo:nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
|
||||
}
|
||||
|
||||
- (void)checkConfig {
|
||||
- (void)updateFromConfig {
|
||||
|
||||
// iCloud enabled / disabled
|
||||
BOOL iCloudEnabled = [[MPiOSConfig get].iCloudEnabled boolValue];
|
||||
@@ -600,7 +542,7 @@
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
||||
NSError *error = nil;
|
||||
if ((siteCount = [context countForFetchRequest:fetchRequest error:&error]) == NSNotFound) {
|
||||
wrn(@"Couldn't count current sites: %@", error);
|
||||
wrn( @"Couldn't count current sites: %@", error );
|
||||
return;
|
||||
}
|
||||
}];
|
||||
@@ -617,11 +559,11 @@
|
||||
@"or overwrite them with your current sites."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
setConfirmationAnswer( NO );
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
setConfirmationAnswer( YES );
|
||||
}
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
setConfirmationAnswer( NO );
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
setConfirmationAnswer( YES );
|
||||
}
|
||||
cancelTitle:@"Use Old" otherTitles:@"Overwrite", nil];
|
||||
}];
|
||||
else
|
||||
@@ -631,7 +573,7 @@
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
||||
NSError *error = nil;
|
||||
if ((siteCount = [context countForFetchRequest:fetchRequest error:&error]) == NSNotFound) {
|
||||
wrn(@"Couldn't count current sites: %@", error);
|
||||
wrn( @"Couldn't count current sites: %@", error );
|
||||
return;
|
||||
}
|
||||
}];
|
||||
@@ -647,11 +589,11 @@
|
||||
@"or overwrite them with your current iCloud sites."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
setConfirmationAnswer( NO );
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
setConfirmationAnswer( YES );
|
||||
}
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
setConfirmationAnswer( NO );
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
setConfirmationAnswer( YES );
|
||||
}
|
||||
cancelTitle:@"Use Old" otherTitles:@"Overwrite", nil];
|
||||
}];
|
||||
}
|
||||
@@ -711,23 +653,22 @@
|
||||
@"helpHidden" : @([[MPiOSConfig get].helpHidden boolValue]),
|
||||
@"showQuickStart" : @([[MPiOSConfig get].showSetup boolValue]),
|
||||
@"firstRun" : @([[PearlConfig get].firstRun boolValue]),
|
||||
@"launchCount" : NilToNSNull([PearlConfig get].launchCount),
|
||||
@"launchCount" : NilToNSNull( [PearlConfig get].launchCount ),
|
||||
@"askForReviews" : @([[PearlConfig get].askForReviews boolValue]),
|
||||
@"reviewAfterLaunches" : NilToNSNull([PearlConfig get].reviewAfterLaunches),
|
||||
@"reviewedVersion" : NilToNSNull([PearlConfig get].reviewedVersion)
|
||||
@"reviewAfterLaunches" : NilToNSNull( [PearlConfig get].reviewAfterLaunches ),
|
||||
@"reviewedVersion" : NilToNSNull( [PearlConfig get].reviewedVersion )
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UbiquityStoreManager
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
|
||||
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[self.handleCloudContentAlert cancelAlertAnimated:YES];
|
||||
if (![self.storeLoading isVisible])
|
||||
self.storeLoading = [PearlOverlay showOverlayWithTitle:@"Loading Sites"];
|
||||
if (!self.storeLoadingOverlay)
|
||||
self.storeLoadingOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Loading Sites"];
|
||||
} );
|
||||
|
||||
[super ubiquityStoreManager:manager willLoadStoreIsCloud:isCloudStore];
|
||||
@@ -739,32 +680,49 @@
|
||||
[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.storeLoadingOverlay 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.storeLoadingOverlay 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 || self.fixCloudContentAlert)) {
|
||||
[self.storeLoadingOverlay cancelOverlayAnimated:YES];
|
||||
[self.handleCloudDisabledAlert cancelAlertAnimated:YES];
|
||||
[self showCloudContentAlert];
|
||||
};
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)ubiquityStoreManagerHandleCloudDisabled:(UbiquityStoreManager *)manager {
|
||||
|
||||
if (!self.handleCloudDisabledAlert)
|
||||
self.handleCloudDisabledAlert = [PearlAlert showAlertWithTitle:@"iCloud Login" message:
|
||||
@"You haven't added an iCloud account to your device yet.\n"
|
||||
@"To add one, go into Apple's Settings -> iCloud."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == alert.firstOtherButtonIndex) {
|
||||
[MPiOSConfig get].iCloudEnabled = @NO;
|
||||
return;
|
||||
}
|
||||
|
||||
[self.storeManager reloadStore];
|
||||
} cancelTitle:@"Try Again" otherTitles:@"Disable iCloud", nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)showCloudContentAlert {
|
||||
|
||||
__weak MPiOSAppDelegate *wSelf = self;
|
||||
@@ -774,52 +732,29 @@
|
||||
message:@"Waiting for your other device to auto‑correct the problem..."
|
||||
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:
|
||||
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
wSelf.fixCloudContentAlert = [PearlAlert showAlertWithTitle:@"Fix iCloud Now" message:
|
||||
@"This problem can be auto‑corrected by opening the app on another device where you recently made changes.\n"
|
||||
@"You can fix the problem from this device anyway, but recent changes from another device might get lost.\n\n"
|
||||
@"You can also turn iCloud off for now."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:
|
||||
^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == alert_.cancelButtonIndex)
|
||||
[wSelf showCloudContentAlert];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex])
|
||||
[wSelf.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:YES];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
[MPiOSConfig get].iCloudEnabled = @NO;
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonBack
|
||||
otherTitles:@"Fix Anyway",
|
||||
@"Turn Off", nil];
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[MPiOSConfig get].iCloudEnabled = @NO;
|
||||
} cancelTitle:nil otherTitles:@"Fix Now", @"Turn Off", nil];
|
||||
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||
wSelf.fixCloudContentAlert = [PearlAlert showAlertWithTitle:@"Fix iCloud Now" message:
|
||||
@"This problem can be auto‑corrected by opening the app on another device where you recently made changes.\n"
|
||||
@"You can fix the problem from this device anyway, but recent changes from another device might get lost.\n\n"
|
||||
@"You can also turn iCloud off for now."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
initAlert:nil tappedButtonBlock:
|
||||
^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == alert_.cancelButtonIndex)
|
||||
[wSelf showCloudContentAlert];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex])
|
||||
[wSelf.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:YES];
|
||||
if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1)
|
||||
[MPiOSConfig get].iCloudEnabled = @NO;
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonBack
|
||||
otherTitles:@"Fix Anyway",
|
||||
@"Turn Off", nil];
|
||||
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||
[MPiOSConfig get].iCloudEnabled = @NO;
|
||||
} cancelTitle:nil otherTitles:@"Fix Now", @"Turn Off", nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Google+
|
||||
|
||||
- (NSDictionary *)googlePlusInfo {
|
||||
|
||||
static NSDictionary *googlePlusInfo = nil;
|
||||
if (googlePlusInfo == nil)
|
||||
googlePlusInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Google+" withExtension:@"plist"]];
|
||||
|
||||
return googlePlusInfo;
|
||||
}
|
||||
|
||||
- (NSString *)googlePlusClientID {
|
||||
|
||||
NSString *googlePlusClientID = NSNullToNil([[self googlePlusInfo] valueForKeyPath:@"ClientID"]);
|
||||
if (![googlePlusClientID length])
|
||||
wrn(@"Google+ client ID not set. User won't be able to share via Google+.");
|
||||
|
||||
return googlePlusClientID;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TestFlight
|
||||
|
||||
- (NSDictionary *)testFlightInfo {
|
||||
@@ -834,14 +769,13 @@
|
||||
|
||||
- (NSString *)testFlightToken {
|
||||
|
||||
NSString *testFlightToken = NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Application Token"]);
|
||||
NSString *testFlightToken = NSNullToNil( [[self testFlightInfo] valueForKeyPath:@"Application Token"] );
|
||||
if (![testFlightToken length])
|
||||
wrn(@"TestFlight token not set. Test Flight won't be aware of this test.");
|
||||
wrn( @"TestFlight token not set. Test Flight won't be aware of this test." );
|
||||
|
||||
return testFlightToken;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Crashlytics
|
||||
|
||||
- (NSDictionary *)crashlyticsInfo {
|
||||
@@ -856,14 +790,13 @@
|
||||
|
||||
- (NSString *)crashlyticsAPIKey {
|
||||
|
||||
NSString *crashlyticsAPIKey = NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
|
||||
NSString *crashlyticsAPIKey = NSNullToNil( [[self crashlyticsInfo] valueForKeyPath:@"API Key"] );
|
||||
if (![crashlyticsAPIKey length])
|
||||
wrn(@"Crashlytics API key not set. Crash logs won't be recorded.");
|
||||
wrn( @"Crashlytics API key not set. Crash logs won't be recorded." );
|
||||
|
||||
return crashlyticsAPIKey;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Localytics
|
||||
|
||||
- (NSDictionary *)localyticsInfo {
|
||||
@@ -879,12 +812,12 @@
|
||||
- (NSString *)localyticsKey {
|
||||
|
||||
#ifdef DEBUG
|
||||
NSString *localyticsKey = NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
|
||||
NSString *localyticsKey = NSNullToNil( [[self localyticsInfo] valueForKeyPath:@"Key.development"] );
|
||||
#else
|
||||
NSString *localyticsKey = NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
|
||||
#endif
|
||||
if (![localyticsKey length])
|
||||
wrn(@"Localytics key not set. Demographics won't be collected.");
|
||||
wrn( @"Localytics key not set. Demographics won't be collected." );
|
||||
|
||||
return localyticsKey;
|
||||
}
|
||||
|
@@ -18,5 +18,6 @@
|
||||
@property(nonatomic, retain) NSNumber *loginNameTipShown;
|
||||
@property(nonatomic, retain) NSNumber *traceMode;
|
||||
@property(nonatomic, retain) NSNumber *iCloudEnabled;
|
||||
@property(nonatomic, retain) NSNumber *dictationSearch;
|
||||
|
||||
@end
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
@implementation MPiOSConfig
|
||||
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, iCloudEnabled;
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, iCloudEnabled, dictationSearch;
|
||||
|
||||
- (id)init {
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
NSStringFromSelector( @selector(typeTipShown) ) : @(!self.firstRun),
|
||||
NSStringFromSelector( @selector(loginNameTipShown) ) : @NO,
|
||||
NSStringFromSelector( @selector(traceMode) ) : @NO,
|
||||
NSStringFromSelector( @selector(iCloudEnabled) ) : @NO
|
||||
NSStringFromSelector( @selector(iCloudEnabled) ) : @NO,
|
||||
NSStringFromSelector( @selector( dictationSearch) ) : @NO
|
||||
}];
|
||||
|
||||
return self;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="4514" systemVersion="13C64" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="5056" systemVersion="13C64" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1536" identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
|
||||
<deployment defaultVersion="1792" identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Type View Controller - Type-->
|
||||
@@ -470,15 +470,15 @@ Your passwords will be AES-encrypted with your master password.</string>
|
||||
<objects>
|
||||
<viewController id="Tx0-mM-kHk" customClass="MPLogsViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="3oc-v8-YGP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" id="mtJ-9r-6yT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" id="ojc-Tn-DM1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="524"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<string key="text">119-20:51:52 MPiOSAppDelegate.m:36 | INFO : Initializing TestFlight
|
||||
@@ -508,7 +508,7 @@ Your passwords will be AES-encrypted with your master password.</string>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<toolbar contentMode="scaleToFill" barStyle="black" translucent="NO" id="QPO-l8-Opz">
|
||||
<rect key="frame" x="0.0" y="524" width="320" height="44"/>
|
||||
<rect key="frame" x="0.0" y="460" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<items>
|
||||
<barButtonItem systemItem="compose" id="Yvq-If-VqG">
|
||||
@@ -1198,6 +1198,7 @@ L4m3P4sSw0rD</string>
|
||||
<navigationItem key="navigationItem" id="zZZ-QZ-Yur"/>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="305" height="402"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="hkm-U7-Dm7" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
@@ -1640,6 +1641,7 @@ You can use the words in the background for inspiration in finding a memorable m
|
||||
<outlet property="uiContainer" destination="PHH-XC-9QQ" id="ogr-ak-h1V"/>
|
||||
<outlet property="wordWall" destination="JTj-nh-BWs" id="Mdy-OW-ouS"/>
|
||||
<segue destination="PQa-Xl-A3x" kind="push" identifier="MP_Unlock" id="wiY-Sg-Ba4"/>
|
||||
<segue destination="xq0-wq-JCC" kind="push" id="EtC-ot-s1R"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="OGA-5j-IcQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@@ -1823,6 +1825,112 @@ You can use the words in the background for inspiration in finding a memorable m
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="145"/>
|
||||
</scene>
|
||||
<!--Combined View Controller-->
|
||||
<scene sceneID="QH8-Nk-KaH">
|
||||
<objects>
|
||||
<viewController id="xq0-wq-JCC" customClass="MPCombinedViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="j8f-w6-4eY">
|
||||
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" id="lbt-4z-QGi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="504"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" id="LpM-29-IaB" userLabel="View - MP Entry">
|
||||
<rect key="frame" x="20" y="158" width="280" height="88"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your full name:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="jrz-j5-Wyq">
|
||||
<rect key="frame" x="10" y="0.0" width="260" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="P96-bw-UwF">
|
||||
<rect key="frame" x="0.0" y="28" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
|
||||
</imageView>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="vF6-yj-Fi1">
|
||||
<rect key="frame" x="10" y="28" width="260" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
|
||||
<textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Nbn-Rv-sP1" id="7AG-Oh-4fi"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" id="8R1-SW-Eup">
|
||||
<rect key="frame" x="0.0" y="20" width="320" height="130"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="Z2n-2Z-E66">
|
||||
<size key="itemSize" width="160" height="110"/>
|
||||
<size key="headerReferenceSize" width="80" height="50"/>
|
||||
<size key="footerReferenceSize" width="80" height="50"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="10"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="wxJ-sJ-2eh">
|
||||
<rect key="frame" x="80" y="10" width="160" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="160" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" selected="YES" contentHorizontalAlignment="center" contentVerticalAlignment="top" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="xh4-cC-tlZ">
|
||||
<rect key="frame" x="25" y="0.0" width="110" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" red="0.40000000600000002" green="0.80000001190000003" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<state key="normal" backgroundImage="avatar-0.png">
|
||||
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="QP7-AZ-N0k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="130"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</collectionReusableView>
|
||||
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="dUs-OO-Rlt">
|
||||
<rect key="frame" x="240" y="0.0" width="80" height="130"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</collectionReusableView>
|
||||
</collectionView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" id="SlG-Kl-hMc">
|
||||
<rect key="frame" x="90" y="77" width="140" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="Futura-CondensedExtraBold" family="Futura" pointSize="13"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="Mc0-uv-zDi">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="VbW-OM-624"/>
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="if6-Lk-tcu"/>
|
||||
</navigationItem>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="WvC-pQ-beY" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="785"/>
|
||||
</scene>
|
||||
<!--Pearl Navigation Controller-->
|
||||
<scene sceneID="wrY-9D-LEc">
|
||||
<objects>
|
||||
@@ -1873,11 +1981,11 @@ You can use the words in the background for inspiration in finding a memorable m
|
||||
<rect key="frame" x="20" y="137" width="280" height="288"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<mutableString key="text">Enabling iCloud will keep all your iPhones, iPads and Macs nicely in-sync. Any site you add on this device will automatically appear on all your others as well.
|
||||
<string key="text">Enabling iCloud will keep all your iPhones, iPads and Macs nicely in-sync. Any site you add on this device will automatically appear on all your others as well.
|
||||
|
||||
Note that even without iCloud syncing, you can make your passwords available from any device by simply creating the same user on all your devices. Enabling iCloud is mainly benefitial to keep the list of sites you use in sync on all your devices.
|
||||
|
||||
Only site names and custom passwords are sent to iCloud. Passwords are encrypted with your master password and illegible by Apple or any interceptor.</mutableString>
|
||||
Only site names and custom passwords are sent to iCloud. Passwords are encrypted with your master password and illegible by Apple or any interceptor.</string>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
@@ -2158,7 +2266,7 @@ Only site names and custom passwords are sent to iCloud. Passwords are encrypte
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<color key="onTintColor" red="0.37254901959999998" green="0.3921568627" blue="0.42745098040000001" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<connections>
|
||||
<action selector="didToggleSwitch:" destination="oLN-6u-GLb" eventType="valueChanged" id="e5q-mf-XK7"/>
|
||||
<action selector="valueChanged:" destination="oLN-6u-GLb" eventType="valueChanged" id="e5q-mf-XK7"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
@@ -2215,7 +2323,7 @@ Only site names and custom passwords are sent to iCloud. Passwords are encrypte
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="qoQ-x9-XHs">
|
||||
<rect key="frame" x="20" y="0.0" width="280" height="155"/>
|
||||
<rect key="frame" x="19" y="0.0" width="280" height="155"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<string key="text">"Save Password" will store your master password in the keychain. This means you won't need the master password anymore to log in (unless your device gets restored or you start using a new device). This is somewhat less secure in the event of theft. You can compensate for the reduced security by going into Settings and navigating: General->Passcode Lock, disabling "Simple Passcode", and setting a more secure passcode for your device.</string>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
@@ -2439,6 +2547,7 @@ Only site names and custom passwords are sent to iCloud. Passwords are encrypte
|
||||
<navigationItem key="navigationItem" id="Pm8-fx-hfM"/>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="305" height="402"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="LHv-Mk-8Kp" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
@@ -3061,9 +3170,9 @@ However, it means that anyone who finds your device unlocked can do the same.</s
|
||||
<simulatedScreenMetrics key="destination" type="retina4"/>
|
||||
</simulatedMetricsContainer>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="jgo-j3-gbW"/>
|
||||
<segue reference="hxY-aA-ngI"/>
|
||||
<segue reference="9Bs-cD-ddF"/>
|
||||
<segue reference="X2A-e4-krq"/>
|
||||
<segue reference="KIl-ZW-M7G"/>
|
||||
<segue reference="5Im-dm-qfS"/>
|
||||
<segue reference="jgo-j3-gbW"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
</document>
|
||||
|
@@ -71,14 +71,15 @@
|
||||
</dict>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Exo-Bold.otf</string>
|
||||
<string>Exo-ExtraBold.otf</string>
|
||||
<string>Exo-Regular.otf</string>
|
||||
<string>Exo2.0-Bold.otf</string>
|
||||
<string>Exo2.0-ExtraBold.otf</string>
|
||||
<string>Exo2.0-Regular.otf</string>
|
||||
<string>Exo2.0-Thin.otf</string>
|
||||
<string>SourceCodePro-Black.otf</string>
|
||||
<string>SourceCodePro-ExtraLight.otf</string>
|
||||
</array>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>MainStoryboard_iPhone</string>
|
||||
<string>Storyboard</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,16 @@
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-com.apple.coredata.ubiquity.logLevel"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "3"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
|
@@ -70,6 +70,24 @@
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Enabling support for dictation in the site search box will enable the dictation button next to the space bar at the bottom of the keyboard. Press this button and speak the name of your site to look it up. Enabling dictation will change your keyboard which might make it slightly more difficult to enter a site name manually.</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
<key>Key</key>
|
||||
<string>dictationSearch</string>
|
||||
<key>Title</key>
|
||||
<string>Dictation Search</string>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
@@ -88,6 +106,24 @@
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>FooterText</key>
|
||||
<string>If the app tends to crash on login, enable this to check if there are any inconsistencies in your site data. It may slow down login a bit, so keep it off when no issues are reported on login.</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Check For Inconsistencies</string>
|
||||
<key>Key</key>
|
||||
<string>checkInconsistency</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Root</string>
|
||||
|
2784
MasterPassword/ObjC/iOS/Storyboard.storyboard
Normal file
2784
MasterPassword/ObjC/iOS/Storyboard.storyboard
Normal file
File diff suppressed because it is too large
Load Diff
BIN
MasterPassword/Resources/Media/Avatars/avatar-add.png
Normal file
BIN
MasterPassword/Resources/Media/Avatars/avatar-add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
MasterPassword/Resources/Media/Avatars/avatar-add@2x.png
Normal file
BIN
MasterPassword/Resources/Media/Avatars/avatar-add@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-Bold.otf
Normal file
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-Bold.otf
Normal file
Binary file not shown.
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-ExtraBold.otf
Normal file
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-ExtraBold.otf
Normal file
Binary file not shown.
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-Regular.otf
Normal file
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-Regular.otf
Normal file
Binary file not shown.
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-Thin.otf
Normal file
BIN
MasterPassword/Resources/Media/Fonts/Exo2.0-Thin.otf
Normal file
Binary file not shown.
BIN
MasterPassword/Resources/Media/empty.png
Normal file
BIN
MasterPassword/Resources/Media/empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 B |
BIN
MasterPassword/Resources/Media/empty@2x.png
Normal file
BIN
MasterPassword/Resources/Media/empty@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 B |
BIN
MasterPassword/Resources/Media/keyboard-dark@2x.png
Normal file
BIN
MasterPassword/Resources/Media/keyboard-dark@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
Reference in New Issue
Block a user