Compare commits
52 Commits
2.1-releas
...
2.1-appsto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
543ebd4bac | ||
|
|
e6d21e1c1d | ||
|
|
a3ebcf0608 | ||
|
|
556d1d3d58 | ||
|
|
979d3a2a5a | ||
|
|
480e7f192a | ||
|
|
a18793b161 | ||
|
|
9b24efa65c | ||
|
|
3e217d5a69 | ||
|
|
c8ca1c80e6 | ||
|
|
88c18db010 | ||
|
|
f909cdbae4 | ||
|
|
8b8d5d325e | ||
|
|
c7670f47db | ||
|
|
f3f25f5890 | ||
|
|
3065433a37 | ||
|
|
41b3964363 | ||
|
|
5e8810c535 | ||
|
|
8c3dfc8510 | ||
|
|
b4b9ee3cb9 | ||
|
|
da4bad7977 | ||
|
|
984434cca4 | ||
|
|
064122f36d | ||
|
|
5db083bf7c | ||
|
|
44f91e0618 | ||
|
|
6050b5d6fd | ||
|
|
8e3e77c2c1 | ||
|
|
a2e71aa94d | ||
|
|
a5bc2eb584 | ||
|
|
9bb613a3b6 | ||
|
|
466863f8fd | ||
|
|
fe5828c724 | ||
|
|
b3ec7a848d | ||
|
|
17734652b4 | ||
|
|
9e742fa40f | ||
|
|
d03b1746e0 | ||
|
|
58156be793 | ||
|
|
d5a5cd7de4 | ||
|
|
2100662fb3 | ||
|
|
248627aa92 | ||
|
|
449ccaa3d4 | ||
|
|
0a7465282b | ||
|
|
5b85ba3a4b | ||
|
|
b3a0b6a7c0 | ||
|
|
4396ce436e | ||
|
|
68e6106ee7 | ||
|
|
4c12f368f5 | ||
|
|
0156f8c3c8 | ||
|
|
2e5cbac761 | ||
|
|
a043b7c049 | ||
|
|
06c62f70ed | ||
|
|
bc88daf08d |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,6 +16,9 @@
|
|||||||
xcuserdata/
|
xcuserdata/
|
||||||
/DerivedData/
|
/DerivedData/
|
||||||
|
|
||||||
|
# Generated
|
||||||
|
MasterPassword/Resources/Media/Images.xcassets/
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
Press/Background.png
|
Press/Background.png
|
||||||
Press/Front-Page.png
|
Press/Front-Page.png
|
||||||
|
|||||||
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -4,9 +4,18 @@
|
|||||||
[submodule "External/InAppSettingsKit"]
|
[submodule "External/InAppSettingsKit"]
|
||||||
path = External/InAppSettingsKit
|
path = External/InAppSettingsKit
|
||||||
url = git://github.com/lhunath/InAppSettingsKit.git
|
url = git://github.com/lhunath/InAppSettingsKit.git
|
||||||
[submodule "External/UbiquityStoreManager"]
|
|
||||||
path = External/UbiquityStoreManager
|
|
||||||
url = git://github.com/lhunath/UbiquityStoreManager.git
|
|
||||||
[submodule "External/RHStatusItemView"]
|
[submodule "External/RHStatusItemView"]
|
||||||
path = External/RHStatusItemView
|
path = External/RHStatusItemView
|
||||||
url = git://github.com/lhunath/RHStatusItemView.git
|
url = git://github.com/lhunath/RHStatusItemView.git
|
||||||
|
[submodule "External/KCOrderedAccessorFix"]
|
||||||
|
path = External/KCOrderedAccessorFix
|
||||||
|
url = https://github.com/CFKevinRef/KCOrderedAccessorFix.git
|
||||||
|
[submodule "External/AttributedMarkdown"]
|
||||||
|
path = External/AttributedMarkdown
|
||||||
|
url = https://github.com/dreamwieber/AttributedMarkdown.git
|
||||||
|
[submodule "External/uicolor-utilities"]
|
||||||
|
path = External/uicolor-utilities
|
||||||
|
url = git://github.com/lhunath/uicolor-utilities.git
|
||||||
|
[submodule "External/jrswizzle"]
|
||||||
|
path = External/jrswizzle
|
||||||
|
url = git://github.com/jonmarimba/jrswizzle.git
|
||||||
|
|||||||
1
External/AttributedMarkdown
vendored
Submodule
1
External/AttributedMarkdown
vendored
Submodule
Submodule External/AttributedMarkdown added at d598fb4f5e
1
External/KCOrderedAccessorFix
vendored
Submodule
1
External/KCOrderedAccessorFix
vendored
Submodule
Submodule External/KCOrderedAccessorFix added at e1955221bf
2
External/Pearl
vendored
2
External/Pearl
vendored
Submodule External/Pearl updated: b63670d86d...1fbb8558f0
1
External/UbiquityStoreManager
vendored
1
External/UbiquityStoreManager
vendored
Submodule External/UbiquityStoreManager deleted from 3492749214
1
External/jrswizzle
vendored
Submodule
1
External/jrswizzle
vendored
Submodule
Submodule External/jrswizzle added at 98d18aee73
1
External/uicolor-utilities
vendored
Submodule
1
External/uicolor-utilities
vendored
Submodule
Submodule External/uicolor-utilities added at ae96212a49
@@ -18,8 +18,12 @@
|
|||||||
<string>github.com:Lyndir/Lyndir.git</string>
|
<string>github.com:Lyndir/Lyndir.git</string>
|
||||||
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
|
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
|
||||||
<string>git://github.com/lhunath/uicolor-utilities.git</string>
|
<string>git://github.com/lhunath/uicolor-utilities.git</string>
|
||||||
|
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
|
||||||
|
<string>https://github.com/CFKevinRef/KCOrderedAccessorFix.git</string>
|
||||||
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
|
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
|
||||||
<string>git://github.com/lhunath/RHStatusItemView.git</string>
|
<string>git://github.com/lhunath/RHStatusItemView.git</string>
|
||||||
|
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
|
||||||
|
<string>https://github.com/dreamwieber/AttributedMarkdown.git</string>
|
||||||
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
|
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
|
||||||
<string>ssh://github.com/Lyndir/Pearl.git</string>
|
<string>ssh://github.com/Lyndir/Pearl.git</string>
|
||||||
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
|
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
|
||||||
@@ -41,8 +45,12 @@
|
|||||||
<string>../..</string>
|
<string>../..</string>
|
||||||
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
|
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
|
||||||
<string>../External/Pearl/External/uicolor-utilities</string>
|
<string>../External/Pearl/External/uicolor-utilities</string>
|
||||||
|
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
|
||||||
|
<string>../External/KCOrderedAccessorFix</string>
|
||||||
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
|
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
|
||||||
<string>../External/RHStatusItemView</string>
|
<string>../External/RHStatusItemView</string>
|
||||||
|
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
|
||||||
|
<string>../External/AttributedMarkdown</string>
|
||||||
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
|
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
|
||||||
<string>../External/Pearl</string>
|
<string>../External/Pearl</string>
|
||||||
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
|
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
|
||||||
@@ -68,6 +76,14 @@
|
|||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
|
<string>public.vcs.git</string>
|
||||||
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
|
<string>3ED8592497DB6A564366943C9AAD5A46341B5076</string>
|
||||||
|
<key>IDESourceControlWCCName</key>
|
||||||
|
<string>AttributedMarkdown</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
@@ -84,6 +100,14 @@
|
|||||||
<key>IDESourceControlWCCName</key>
|
<key>IDESourceControlWCCName</key>
|
||||||
<string>jrswizzle</string>
|
<string>jrswizzle</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
|
<string>public.vcs.git</string>
|
||||||
|
<key>IDESourceControlWCCIdentifierKey</key>
|
||||||
|
<string>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</string>
|
||||||
|
<key>IDESourceControlWCCName</key>
|
||||||
|
<string>KCOrderedAccessorFix</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||||
<string>public.vcs.git</string>
|
<string>public.vcs.git</string>
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ void usage() {
|
|||||||
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
|
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
|
||||||
" b, basic | 8 characters, no symbols.\n"
|
" b, basic | 8 characters, no symbols.\n"
|
||||||
" s, short | Copy-friendly, 4 characters, no symbols.\n"
|
" s, short | Copy-friendly, 4 characters, no symbols.\n"
|
||||||
" p, pin | 4 numbers.\n"
|
" i, pin | 4 numbers.\n"
|
||||||
" n, name | 9 letter name.\n\n", MP_env_sitetype);
|
" n, name | 9 letter name.\n"
|
||||||
|
" p, phrase | 20 character sentence.\n\n", MP_env_sitetype);
|
||||||
fprintf(stderr, " -c counter The value of the counter.\n"
|
fprintf(stderr, " -c counter The value of the counter.\n"
|
||||||
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
|
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
|
||||||
fprintf(stderr, " -v variant The kind of content to generate.\n"
|
fprintf(stderr, " -v variant The kind of content to generate.\n"
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ const MPElementType TypeWithName(const char *typeName) {
|
|||||||
return MPElementTypeGeneratedBasic;
|
return MPElementTypeGeneratedBasic;
|
||||||
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
|
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
|
||||||
return MPElementTypeGeneratedShort;
|
return MPElementTypeGeneratedShort;
|
||||||
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "pin"))
|
if (0 == strcmp(lowerTypeName, "i") || 0 == strcmp(lowerTypeName, "pin"))
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPElementTypeGeneratedPIN;
|
||||||
if (0 == strcmp(lowerTypeName, "n") || 0 == strcmp(lowerTypeName, "name"))
|
if (0 == strcmp(lowerTypeName, "n") || 0 == strcmp(lowerTypeName, "name"))
|
||||||
return MPElementTypeGeneratedName;
|
return MPElementTypeGeneratedName;
|
||||||
|
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "phrase"))
|
||||||
|
return MPElementTypeGeneratedPhrase;
|
||||||
|
|
||||||
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
|
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
|
||||||
abort();
|
abort();
|
||||||
@@ -72,6 +74,10 @@ const char *CipherForType(MPElementType type, uint8_t seedByte) {
|
|||||||
case MPElementTypeGeneratedName: {
|
case MPElementTypeGeneratedName: {
|
||||||
return "cvccvcvcv";
|
return "cvccvcvcv";
|
||||||
}
|
}
|
||||||
|
case MPElementTypeGeneratedPhrase: {
|
||||||
|
char *ciphers[] = { "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
|
||||||
|
return ciphers[seedByte % 3];
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
fprintf(stderr, "Unknown generated type: %d", type);
|
fprintf(stderr, "Unknown generated type: %d", type);
|
||||||
abort();
|
abort();
|
||||||
@@ -148,6 +154,10 @@ const char CharacterFromClass(char characterClass, uint8_t seedByte) {
|
|||||||
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ' ': {
|
||||||
|
classCharacters = " ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
fprintf(stderr, "Unknown character class: %c", characterClass);
|
fprintf(stderr, "Unknown character class: %c", characterClass);
|
||||||
abort();
|
abort();
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ typedef enum {
|
|||||||
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedName = 0xF | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeGeneratedName = 0xE | MPElementTypeClassGenerated | 0x0,
|
||||||
|
MPElementTypeGeneratedPhrase = 0xF | MPElementTypeClassGenerated | 0x0,
|
||||||
|
|
||||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
||||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
||||||
|
|||||||
@@ -16,10 +16,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPKey.h"
|
#import "MPKey.h"
|
||||||
#import "MPElementStoredEntity.h"
|
#import "MPStoredSiteEntity.h"
|
||||||
#import "MPElementGeneratedEntity.h"
|
#import "MPGeneratedSiteEntity.h"
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
|
|
||||||
#define MPAlgorithmDefaultVersion 1
|
#define MPAlgorithmDefaultVersion 2
|
||||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
||||||
|
|
||||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
||||||
@@ -43,49 +44,56 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
|||||||
|
|
||||||
@required
|
@required
|
||||||
- (NSUInteger)version;
|
- (NSUInteger)version;
|
||||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||||
|
|
||||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
|
||||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||||
|
|
||||||
- (NSString *)scopeForVariant:(MPElementVariant)variant;
|
- (NSString *)scopeForVariant:(MPSiteVariant)variant;
|
||||||
- (NSString *)nameOfType:(MPElementType)type;
|
- (NSString *)nameOfType:(MPSiteType)type;
|
||||||
- (NSString *)shortNameOfType:(MPElementType)type;
|
- (NSString *)shortNameOfType:(MPSiteType)type;
|
||||||
- (NSString *)classNameOfType:(MPElementType)type;
|
- (NSString *)classNameOfType:(MPSiteType)type;
|
||||||
- (Class)classOfType:(MPElementType)type;
|
- (Class)classOfType:(MPSiteType)type;
|
||||||
- (NSArray *)allTypes;
|
- (NSArray *)allTypes;
|
||||||
- (NSArray *)allTypesStartingWith:(MPElementType)startingType;
|
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
|
||||||
- (MPElementType)nextType:(MPElementType)type;
|
- (MPSiteType)nextType:(MPSiteType)type;
|
||||||
- (MPElementType)previousType:(MPElementType)type;
|
- (MPSiteType)previousType:(MPSiteType)type;
|
||||||
|
|
||||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
|
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
|
||||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
|
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
usingKey:(MPKey *)key;
|
usingKey:(MPKey *)key;
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
|
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
|
||||||
variant:(MPElementVariant)variant usingKey:(MPKey *)key;
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
|
||||||
|
|
||||||
- (NSString *)storedLoginForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
|
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
- (NSString *)storedPasswordForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
|
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
|
||||||
|
|
||||||
- (BOOL)savePassword:(NSString *)clearPassword toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
|
||||||
- (NSString *)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
- (NSString *)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
|
||||||
|
|
||||||
- (void)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
- (void)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
||||||
result:(void ( ^ )(NSString *result))resultBlock;
|
result:(void ( ^ )(NSString *result))resultBlock;
|
||||||
|
|
||||||
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
|
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
|
||||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
- (void)importClearTextPassword:(NSString *)clearPassword intoElement:(MPElementEntity *)element
|
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
|
||||||
usingKey:(MPKey *)elementKey;
|
usingKey:(MPKey *)siteKey;
|
||||||
- (NSString *)exportPasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPElementType)type byAttacker:(MPAttacker)attacker;
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
|
|||||||
versionToAlgorithm = [NSMutableDictionary dictionary];
|
versionToAlgorithm = [NSMutableDictionary dictionary];
|
||||||
|
|
||||||
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
|
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
|
||||||
if (!algorithm) if ((algorithm = [NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
|
if (!algorithm && (algorithm = (id<MPAlgorithm>)[NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
|
||||||
versionToAlgorithm[@(version)] = algorithm;
|
versionToAlgorithm[@(version)] = algorithm;
|
||||||
|
|
||||||
return algorithm;
|
return algorithm;
|
||||||
@@ -35,6 +35,9 @@ id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
|
|||||||
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
|
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
|
||||||
// Pre-1.3
|
// Pre-1.3
|
||||||
return MPAlgorithmForVersion( 0 );
|
return MPAlgorithmForVersion( 0 );
|
||||||
|
if (PearlCFBundleVersionCompare( bundleVersion, @"2.1" ) == NSOrderedAscending)
|
||||||
|
// Pre-2.1
|
||||||
|
return MPAlgorithmForVersion( 1 );
|
||||||
|
|
||||||
return MPAlgorithmDefault;
|
return MPAlgorithmDefault;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
|
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
|
||||||
|
|
||||||
- (NSDictionary *)allCiphers;
|
- (NSDictionary *)allCiphers;
|
||||||
- (NSArray *)ciphersForType:(MPElementType)type;
|
- (NSArray *)ciphersForType:(MPSiteType)type;
|
||||||
- (NSArray *)cipherClasses;
|
- (NSArray *)cipherClasses;
|
||||||
- (NSArray *)cipherClassCharacters;
|
- (NSArray *)cipherClassCharacters;
|
||||||
- (NSString *)charactersForCipherClass:(NSString *)cipherClass;
|
- (NSString *)charactersForCipherClass:(NSString *)cipherClass;
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
#import "MPAlgorithmV0.h"
|
#import "MPAlgorithmV0.h"
|
||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
|
#import "MPAppDelegate_Shared.h"
|
||||||
|
#import "MPAppDelegate_InApp.h"
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
#include <openssl/bn.h>
|
#include <openssl/bn.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
@@ -70,40 +73,40 @@
|
|||||||
return [(id<MPAlgorithm>)other version] == [self version];
|
return [(id<MPAlgorithm>)other version] == [self version];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
|
||||||
NSArray *migrationElements = [moc executeFetchRequest:migrationRequest error:&error];
|
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
|
||||||
if (!migrationElements) {
|
if (!migrationSites) {
|
||||||
err( @"While looking for elements to migrate: %@", error );
|
err( @"While looking for sites to migrate: %@", [error fullDescription] );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL requiresExplicitMigration = NO;
|
BOOL success = YES;
|
||||||
for (MPElementEntity *migrationElement in migrationElements)
|
for (MPSiteEntity *migrationSite in migrationSites)
|
||||||
if (![migrationElement migrateExplicitly:NO])
|
if (![migrationSite tryMigrateExplicitly:NO])
|
||||||
requiresExplicitMigration = YES;
|
success = NO;
|
||||||
|
|
||||||
return requiresExplicitMigration;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
if (element.version != [self version] - 1)
|
if (site.version != [self version] - 1)
|
||||||
// Only migrate from previous version.
|
// Only migrate from previous version.
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
// This migration requires explicit permission.
|
// This migration requires explicit permission.
|
||||||
element.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply migration.
|
// Apply migration.
|
||||||
element.requiresExplicitMigration = NO;
|
site.requiresExplicitMigration = NO;
|
||||||
element.version = [self version];
|
site.version = [self version];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,129 +139,140 @@
|
|||||||
return [keyData hashWith:MP_hash];
|
return [keyData hashWith:MP_hash];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)scopeForVariant:(MPElementVariant)variant {
|
- (NSString *)scopeForVariant:(MPSiteVariant)variant {
|
||||||
|
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case MPElementVariantPassword:
|
case MPSiteVariantPassword:
|
||||||
return @"com.lyndir.masterpassword";
|
return @"com.lyndir.masterpassword";
|
||||||
case MPElementVariantLogin:
|
case MPSiteVariantLogin:
|
||||||
return @"com.lyndir.masterpassword.login";
|
return @"com.lyndir.masterpassword.login";
|
||||||
|
case MPSiteVariantAnswer:
|
||||||
|
return @"com.lyndir.masterpassword.answer";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Unsupported variant: %ld", (long)variant );
|
Throw( @"Unsupported variant: %ld", (long)variant );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)nameOfType:(MPElementType)type {
|
- (NSString *)nameOfType:(MPSiteType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return @"Maximum Security Password";
|
return @"Maximum Security Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return @"Long Password";
|
return @"Long Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return @"Medium Password";
|
return @"Medium Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return @"Basic Password";
|
return @"Basic Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return @"Short Password";
|
return @"Short Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPElementTypeGeneratedName:
|
case MPSiteTypeGeneratedName:
|
||||||
return @"Login Name";
|
return @"Login Name";
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeGeneratedPhrase:
|
||||||
|
return @"Phrase";
|
||||||
|
|
||||||
|
case MPSiteTypeStoredPersonal:
|
||||||
return @"Personal Password";
|
return @"Personal Password";
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return @"Device Private Password";
|
return @"Device Private Password";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)shortNameOfType:(MPElementType)type {
|
- (NSString *)shortNameOfType:(MPSiteType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return @"Maximum";
|
return @"Maximum";
|
||||||
|
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return @"Long";
|
return @"Long";
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return @"Medium";
|
return @"Medium";
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return @"Basic";
|
return @"Basic";
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return @"Short";
|
return @"Short";
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPElementTypeGeneratedName:
|
case MPSiteTypeGeneratedName:
|
||||||
return @"Name";
|
return @"Name";
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeGeneratedPhrase:
|
||||||
|
return @"Phrase";
|
||||||
|
|
||||||
|
case MPSiteTypeStoredPersonal:
|
||||||
return @"Personal";
|
return @"Personal";
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return @"Device";
|
return @"Device";
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)classNameOfType:(MPElementType)type {
|
- (NSString *)classNameOfType:(MPSiteType)type {
|
||||||
|
|
||||||
return NSStringFromClass( [self classOfType:type] );
|
return NSStringFromClass( [self classOfType:type] );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (Class)classOfType:(MPElementType)type {
|
- (Class)classOfType:(MPSiteType)type {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
Throw( @"No type given." );
|
Throw( @"No type given." );
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedName:
|
case MPSiteTypeGeneratedName:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeGeneratedPhrase:
|
||||||
return [MPElementStoredEntity class];
|
return [MPGeneratedSiteEntity class];
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredPersonal:
|
||||||
return [MPElementStoredEntity class];
|
return [MPStoredSiteEntity class];
|
||||||
|
|
||||||
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
|
return [MPStoredSiteEntity class];
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Type not supported: %lu", (long)type );
|
Throw( @"Type not supported: %lu", (long)type );
|
||||||
@@ -266,13 +280,13 @@
|
|||||||
|
|
||||||
- (NSArray *)allTypes {
|
- (NSArray *)allTypes {
|
||||||
|
|
||||||
return [self allTypesStartingWith:MPElementTypeGeneratedMaximum];
|
return [self allTypesStartingWith:MPSiteTypeGeneratedMaximum];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)allTypesStartingWith:(MPElementType)startingType {
|
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
|
||||||
|
|
||||||
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
|
||||||
MPElementType currentType = startingType;
|
MPSiteType currentType = startingType;
|
||||||
do {
|
do {
|
||||||
[allTypes addObject:@(currentType)];
|
[allTypes addObject:@(currentType)];
|
||||||
} while ((currentType = [self nextType:currentType]) != startingType);
|
} while ((currentType = [self nextType:currentType]) != startingType);
|
||||||
@@ -280,33 +294,33 @@
|
|||||||
return allTypes;
|
return allTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)nextType:(MPElementType)type {
|
- (MPSiteType)nextType:(MPSiteType)type {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return MPElementTypeGeneratedMedium;
|
return MPSiteTypeGeneratedMedium;
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return MPElementTypeGeneratedBasic;
|
return MPSiteTypeGeneratedBasic;
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return MPElementTypeGeneratedShort;
|
return MPSiteTypeGeneratedShort;
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPSiteTypeGeneratedPIN;
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return MPElementTypeStoredPersonal;
|
return MPSiteTypeStoredPersonal;
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeStoredPersonal:
|
||||||
return MPElementTypeStoredDevicePrivate;
|
return MPSiteTypeStoredDevicePrivate;
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return MPElementTypeGeneratedMaximum;
|
return MPSiteTypeGeneratedMaximum;
|
||||||
default:
|
default:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)previousType:(MPElementType)type {
|
- (MPSiteType)previousType:(MPSiteType)type {
|
||||||
|
|
||||||
MPElementType previousType = type, nextType = type;
|
MPSiteType previousType = type, nextType = type;
|
||||||
while ((nextType = [self nextType:nextType]) != type)
|
while ((nextType = [self nextType:nextType]) != type)
|
||||||
previousType = nextType;
|
previousType = nextType;
|
||||||
|
|
||||||
@@ -325,7 +339,7 @@
|
|||||||
return ciphers;
|
return ciphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)ciphersForType:(MPElementType)type {
|
- (NSArray *)ciphersForType:(MPSiteType)type {
|
||||||
|
|
||||||
NSString *typeClass = [self classNameOfType:type];
|
NSString *typeClass = [self classNameOfType:type];
|
||||||
NSString *typeName = [self nameOfType:type];
|
NSString *typeName = [self nameOfType:type];
|
||||||
@@ -349,42 +363,51 @@
|
|||||||
|
|
||||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:MPElementTypeGeneratedName withCounter:1
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
||||||
variant:MPElementVariantLogin usingKey:key];
|
variant:MPSiteVariantLogin context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
|
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
usingKey:(MPKey *)key {
|
usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
|
||||||
variant:MPElementVariantPassword usingKey:key];
|
variant:MPSiteVariantPassword context:nil usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
|
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
||||||
variant:(MPElementVariant)variant usingKey:(MPKey *)key {
|
|
||||||
|
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
|
||||||
|
variant:MPSiteVariantAnswer context:question usingKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
NSString *scope = [self scopeForVariant:variant];;
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
[key.keyData encodeBase64], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@ | %@)",
|
||||||
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex], context );
|
||||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||||
[scope dataUsingEncoding:NSUTF8StringEncoding],
|
[scope dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nameLengthBytes,
|
nameLengthBytes,
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
counterBytes,
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nil]
|
nil]
|
||||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
trc( @"seed is: %@", [seed encodeBase64] );
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
const char *seedBytes = seed.bytes;
|
const char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first seed byte.
|
// Determine the cipher from the first seed byte.
|
||||||
NSAssert( [seed length], @"Missing seed." );
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
NSArray *typeCiphers = [self ciphersForType:type];
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
NSString *cipher = typeCiphers[htons( seedBytes[0] ) % [typeCiphers count]];
|
NSString *cipher = typeCiphers[htons( seedBytes[0] ) % [typeCiphers count]];
|
||||||
trc( @"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher );
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the 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." );
|
||||||
@@ -402,79 +425,80 @@
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)storedLoginForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
|
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)storedPasswordForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
|
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self decryptContent:element.contentObject usingKey:key];
|
return [self decryptContent:site.contentObject usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)savePassword:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
case MPElementTypeGeneratedName: {
|
case MPSiteTypeGeneratedName:
|
||||||
NSAssert( NO, @"Cannot save content to element with generated type %lu.", (long)element.type );
|
case MPSiteTypeGeneratedPhrase: {
|
||||||
|
wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
if ([((MPElementStoredEntity *)element).contentObject isEqualToData:encryptedContent])
|
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
((MPElementStoredEntity *)element).contentObject = encryptedContent;
|
((MPStoredSiteEntity *)site).contentObject = encryptedContent;
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
case MPElementTypeStoredDevicePrivate: {
|
case MPSiteTypeStoredDevicePrivate: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||||
if (!encryptedContent)
|
if (!encryptedContent)
|
||||||
[PearlKeyChain deleteItemForQuery:elementQuery];
|
[PearlKeyChain deleteItemForQuery:siteQuery];
|
||||||
else
|
else
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{
|
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
||||||
(__bridge id)kSecValueData : encryptedContent,
|
(__bridge id)kSecValueData : encryptedContent,
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
#endif
|
#endif
|
||||||
}];
|
}];
|
||||||
((MPElementStoredEntity *)element).contentObject = nil;
|
((MPStoredSiteEntity *)site).contentObject = nil;
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Throw( @"Unsupported type: %ld", (long)element.type );
|
Throw( @"Unsupported type: %ld", (long)site.type );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
dispatch_group_t group = dispatch_group_create();
|
||||||
dispatch_group_enter( group );
|
dispatch_group_enter( group );
|
||||||
__block NSString *result = nil;
|
__block NSString *result = nil;
|
||||||
[self resolveLoginForElement:element usingKey:elementKey result:^(NSString *result_) {
|
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
result = result_;
|
result = result_;
|
||||||
dispatch_group_leave( group );
|
dispatch_group_leave( group );
|
||||||
}];
|
}];
|
||||||
@@ -483,12 +507,12 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
dispatch_group_t group = dispatch_group_create();
|
||||||
dispatch_group_enter( group );
|
dispatch_group_enter( group );
|
||||||
__block NSString *result = nil;
|
__block NSString *result = nil;
|
||||||
[self resolvePasswordForElement:element usingKey:elementKey result:^(NSString *result_) {
|
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
result = result_;
|
result = result_;
|
||||||
dispatch_group_leave( group );
|
dispatch_group_leave( group );
|
||||||
}];
|
}];
|
||||||
@@ -497,88 +521,117 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
dispatch_group_t group = dispatch_group_create();
|
||||||
NSString *name = element.name;
|
dispatch_group_enter( group );
|
||||||
BOOL loginGenerated = element.loginGenerated;
|
__block NSString *result = nil;
|
||||||
NSString *loginName = loginGenerated? nil: element.loginName;
|
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||||
|
result = result_;
|
||||||
|
dispatch_group_leave( group );
|
||||||
|
}];
|
||||||
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
dispatch_group_enter( group );
|
||||||
|
__block NSString *result = nil;
|
||||||
|
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
||||||
|
result = result_;
|
||||||
|
dispatch_group_leave( group );
|
||||||
|
}];
|
||||||
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
NSString *name = site.name;
|
||||||
|
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
|
||||||
|
NSString *loginName = loginGenerated? nil: site.loginName;
|
||||||
id<MPAlgorithm> algorithm = nil;
|
id<MPAlgorithm> algorithm = nil;
|
||||||
if (!name.length)
|
if (!name.length)
|
||||||
err( @"Missing name." );
|
err( @"Missing name." );
|
||||||
else if (!elementKey.keyData.length)
|
else if (!siteKey.keyData.length)
|
||||||
err( @"Missing key." );
|
err( @"Missing key." );
|
||||||
else
|
else
|
||||||
algorithm = element.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
if (loginGenerated)
|
if (loginGenerated)
|
||||||
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:elementKey] );
|
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||||
else
|
else
|
||||||
resultBlock( loginName );
|
resultBlock( loginName );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void ( ^ )(NSString *result))resultBlock {
|
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
case MPElementTypeGeneratedName: {
|
case MPSiteTypeGeneratedName:
|
||||||
if (![element isKindOfClass:[MPElementGeneratedEntity class]]) {
|
case MPSiteTypeGeneratedPhrase: {
|
||||||
wrn( @"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.",
|
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||||
(long)element.type, [element class] );
|
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
|
||||||
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *name = element.name;
|
NSString *name = site.name;
|
||||||
MPElementType type = element.type;
|
MPSiteType type = site.type;
|
||||||
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
|
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||||
id<MPAlgorithm> algorithm = nil;
|
id<MPAlgorithm> algorithm = nil;
|
||||||
if (!element.name.length)
|
if (!site.name.length)
|
||||||
err( @"Missing name." );
|
err( @"Missing name." );
|
||||||
else if (!elementKey.keyData.length)
|
else if (!siteKey.keyData.length)
|
||||||
err( @"Missing key." );
|
err( @"Missing key." );
|
||||||
else
|
else
|
||||||
algorithm = element.algorithm;
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:elementKey];
|
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
|
||||||
resultBlock( result );
|
resultBlock( result );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
|
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||||
resultBlock( result );
|
resultBlock( result );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPElementTypeStoredDevicePrivate: {
|
case MPSiteTypeStoredDevicePrivate: {
|
||||||
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
|
NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
|
||||||
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
|
@"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
|
||||||
[element class] );
|
[site class] );
|
||||||
|
|
||||||
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
|
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||||
resultBlock( result );
|
resultBlock( result );
|
||||||
} );
|
} );
|
||||||
break;
|
break;
|
||||||
@@ -586,94 +639,135 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
NSString *name = site.name;
|
||||||
|
id<MPAlgorithm> algorithm = nil;
|
||||||
|
if (!site.name.length)
|
||||||
|
err( @"Missing name." );
|
||||||
|
else if (!siteKey.keyData.length)
|
||||||
|
err( @"Missing key." );
|
||||||
|
else
|
||||||
|
algorithm = site.algorithm;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
|
||||||
|
result:(void ( ^ )(NSString *result))resultBlock {
|
||||||
|
|
||||||
|
NSAssert( [siteKey.keyID isEqualToData:question.site.user.keyID], @"Site does not belong to current user." );
|
||||||
|
NSString *name = question.site.name;
|
||||||
|
NSString *keyword = question.keyword;
|
||||||
|
id<MPAlgorithm> algorithm = nil;
|
||||||
|
if (!name.length)
|
||||||
|
err( @"Missing name." );
|
||||||
|
else if (!siteKey.keyData.length)
|
||||||
|
err( @"Missing key." );
|
||||||
|
else
|
||||||
|
algorithm = question.site.algorithm;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||||
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
case MPElementTypeGeneratedName:
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ([importKey.keyID isEqualToData:elementKey.keyID])
|
if ([importKey.keyID isEqualToData:siteKey.keyID])
|
||||||
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
|
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
|
||||||
|
|
||||||
else {
|
else {
|
||||||
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
||||||
[self importClearTextPassword:clearContent intoElement:element usingKey:elementKey];
|
[self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)importClearTextPassword:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
case MPElementTypeGeneratedName:
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
[self savePassword:clearContent toElement:element usingKey:elementKey];
|
[self savePassword:clearContent toSite:site usingKey:siteKey];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)exportPasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||||
|
|
||||||
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
|
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||||
if (!(element.type & MPElementFeatureExportContent))
|
if (!(site.type & MPSiteFeatureExportContent))
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
NSString *result = nil;
|
NSString *result = nil;
|
||||||
switch (element.type) {
|
switch (site.type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
case MPElementTypeGeneratedName: {
|
case MPSiteTypeGeneratedName:
|
||||||
|
case MPSiteTypeGeneratedPhrase: {
|
||||||
result = nil;
|
result = nil;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal: {
|
case MPSiteTypeStoredPersonal: {
|
||||||
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
|
||||||
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
|
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
|
||||||
(long)element.type, [element class] );
|
(long)site.type, [site class] );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
|
result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MPElementTypeStoredDevicePrivate: {
|
case MPSiteTypeStoredDevicePrivate: {
|
||||||
result = nil;
|
result = nil;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -687,7 +781,7 @@
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
|
||||||
|
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:@{
|
attributes:@{
|
||||||
@@ -708,7 +802,7 @@
|
|||||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPElementType)type byAttacker:(MPAttacker)attacker {
|
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return NO;
|
return NO;
|
||||||
|
|||||||
@@ -25,33 +25,34 @@
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
if (element.version != [self version] - 1)
|
if (site.version != [self version] - 1)
|
||||||
// Only migrate from previous version.
|
// Only migrate from previous version.
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
if (!explicit) {
|
if (!explicit) {
|
||||||
if (element.type & MPElementTypeClassGenerated) {
|
if (site.type & MPSiteTypeClassGenerated) {
|
||||||
// This migration requires explicit permission for types of the generated class.
|
// This migration requires explicit permission for types of the generated class.
|
||||||
element.requiresExplicitMigration = YES;
|
site.requiresExplicitMigration = YES;
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply migration.
|
// Apply migration.
|
||||||
element.requiresExplicitMigration = NO;
|
site.requiresExplicitMigration = NO;
|
||||||
element.version = [self version];
|
site.version = [self version];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
variant:(MPElementVariant)variant usingKey:(MPKey *)key {
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
NSString *scope = [self scopeForVariant:variant];
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||||
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||||
@@ -60,6 +61,8 @@
|
|||||||
nameLengthBytes,
|
nameLengthBytes,
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
counterBytes,
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||||
nil]
|
nil]
|
||||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
trc( @"seed is: %@", [seed encodeHex] );
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
@@ -69,7 +72,7 @@
|
|||||||
NSAssert( [seed length], @"Missing seed." );
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
NSArray *typeCiphers = [self ciphersForType:type];
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||||
trc( @"type %@ (%d), ciphers: %@, selected: %@", [self nameOfType:type], type, typeCiphers, cipher );
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the 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." );
|
||||||
|
|||||||
21
MasterPassword/ObjC/MPAlgorithmV2.h
Normal file
21
MasterPassword/ObjC/MPAlgorithmV2.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPAlgorithmV1
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 17/07/12.
|
||||||
|
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPAlgorithmV1.h"
|
||||||
|
|
||||||
|
@interface MPAlgorithmV2 : MPAlgorithmV1
|
||||||
|
@end
|
||||||
97
MasterPassword/ObjC/MPAlgorithmV2.m
Normal file
97
MasterPassword/ObjC/MPAlgorithmV2.m
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPAlgorithmV1
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 17/07/12.
|
||||||
|
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#import "MPAlgorithmV2.h"
|
||||||
|
#import "MPEntities.h"
|
||||||
|
|
||||||
|
@implementation MPAlgorithmV2
|
||||||
|
|
||||||
|
- (NSUInteger)version {
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||||
|
|
||||||
|
if (site.version != [self version] - 1)
|
||||||
|
// Only migrate from previous version.
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
if (!explicit) {
|
||||||
|
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||||
|
// This migration requires explicit permission for types of the generated class.
|
||||||
|
site.requiresExplicitMigration = YES;
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply migration.
|
||||||
|
site.requiresExplicitMigration = NO;
|
||||||
|
site.version = [self version];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||||
|
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
// Determine the seed whose bytes will be used for calculating a password
|
||||||
|
NSData *nameBytes = [name dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
NSData *contextBytes = [context dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
uint32_t ncounter = htonl( counter ), nnameLength = htonl( nameBytes.length ), ncontextLength = htonl( contextBytes.length );
|
||||||
|
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||||
|
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||||
|
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||||
|
NSString *scope = [self scopeForVariant:variant];
|
||||||
|
NSData *scopeBytes = [scope dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||||
|
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||||
|
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||||
|
scopeBytes,
|
||||||
|
nameLengthBytes,
|
||||||
|
nameBytes,
|
||||||
|
counterBytes,
|
||||||
|
context? contextLengthBytes: nil,
|
||||||
|
contextBytes,
|
||||||
|
nil]
|
||||||
|
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||||
|
trc( @"seed is: %@", [seed encodeHex] );
|
||||||
|
const unsigned char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
|
// Determine the cipher from the first seed byte.
|
||||||
|
NSAssert( [seed length], @"Missing seed." );
|
||||||
|
NSArray *typeCiphers = [self ciphersForType:type];
|
||||||
|
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||||
|
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, 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." );
|
||||||
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
|
uint16_t keyByte = seedBytes[c + 1];
|
||||||
|
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
||||||
|
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
|
||||||
|
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
|
||||||
|
|
||||||
|
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||||
|
[content appendString:character];
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
39
MasterPassword/ObjC/MPAppDelegate_InApp.h
Normal file
39
MasterPassword/ObjC/MPAppDelegate_InApp.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// MPAppDelegate_Key.h
|
||||||
|
// MasterPassword
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 24/11/11.
|
||||||
|
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <StoreKit/StoreKit.h>
|
||||||
|
#import "MPAppDelegate_Shared.h"
|
||||||
|
|
||||||
|
#define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins"
|
||||||
|
#define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers"
|
||||||
|
#define MPProductOSIntegration @"com.lyndir.masterpassword.products.osintegration"
|
||||||
|
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
|
||||||
|
#define MPProductFuel @"com.lyndir.masterpassword.products.fuel"
|
||||||
|
|
||||||
|
#define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */
|
||||||
|
|
||||||
|
@protocol MPInAppDelegate
|
||||||
|
|
||||||
|
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
|
||||||
|
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPAppDelegate_Shared(InApp)
|
||||||
|
|
||||||
|
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||||
|
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
|
||||||
|
|
||||||
|
- (void)reloadProducts;
|
||||||
|
- (BOOL)canMakePayments;
|
||||||
|
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier;
|
||||||
|
|
||||||
|
- (void)restoreCompletedTransactions;
|
||||||
|
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity;
|
||||||
|
|
||||||
|
@end
|
||||||
179
MasterPassword/ObjC/MPAppDelegate_InApp.m
Normal file
179
MasterPassword/ObjC/MPAppDelegate_InApp.m
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
//
|
||||||
|
// MPAppDelegate.m
|
||||||
|
// MasterPassword
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 24/11/11.
|
||||||
|
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPAppDelegate_InApp.h"
|
||||||
|
|
||||||
|
@interface MPAppDelegate_Shared(InApp_Private)<SKProductsRequestDelegate, SKPaymentTransactionObserver>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPAppDelegate_Shared(InApp)
|
||||||
|
|
||||||
|
PearlAssociatedObjectProperty( NSArray*, Products, products );
|
||||||
|
PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers );
|
||||||
|
|
||||||
|
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate {
|
||||||
|
|
||||||
|
if (!self.productObservers)
|
||||||
|
self.productObservers = [NSMutableArray array];
|
||||||
|
[self.productObservers addObject:delegate];
|
||||||
|
|
||||||
|
if (self.products)
|
||||||
|
[delegate updateWithProducts:self.products];
|
||||||
|
else
|
||||||
|
[self reloadProducts];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate {
|
||||||
|
|
||||||
|
[self.productObservers removeObject:delegate];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reloadProducts {
|
||||||
|
|
||||||
|
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:
|
||||||
|
[[NSSet alloc] initWithObjects:MPProductGenerateLogins, MPProductGenerateAnswers, MPProductFuel, nil]];
|
||||||
|
productsRequest.delegate = self;
|
||||||
|
[productsRequest start];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (SKPaymentQueue *)paymentQueue {
|
||||||
|
|
||||||
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once( &once, ^{
|
||||||
|
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
||||||
|
} );
|
||||||
|
|
||||||
|
return [SKPaymentQueue defaultQueue];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)canMakePayments {
|
||||||
|
|
||||||
|
return [SKPaymentQueue canMakePayments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier {
|
||||||
|
|
||||||
|
if (![productIdentifier length])
|
||||||
|
// Missing a product.
|
||||||
|
return NO;
|
||||||
|
if ([productIdentifier isEqualToString:MPProductFuel])
|
||||||
|
// Consumable product.
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
#if ADHOC || DEBUG
|
||||||
|
// All features are unlocked for beta / debug versions.
|
||||||
|
return YES;
|
||||||
|
#else
|
||||||
|
// Check if product is purchased.
|
||||||
|
return [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)restoreCompletedTransactions {
|
||||||
|
|
||||||
|
[[self paymentQueue] restoreCompletedTransactions];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity {
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
if (![[MPAppDelegate_Shared get] canMakePayments]) {
|
||||||
|
[PearlAlert showAlertWithTitle:@"Store Not Set Up" message:
|
||||||
|
@"Try logging using the App Store or from Settings."
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||||
|
tappedButtonBlock:nil cancelTitle:@"Thanks" otherTitles:nil];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (SKProduct *product in self.products)
|
||||||
|
if ([product.productIdentifier isEqualToString:productIdentifier]) {
|
||||||
|
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
|
||||||
|
payment.quantity = quantity;
|
||||||
|
[[self paymentQueue] addPayment:payment];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - SKProductsRequestDelegate
|
||||||
|
|
||||||
|
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
||||||
|
|
||||||
|
inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers );
|
||||||
|
self.products = response.products;
|
||||||
|
|
||||||
|
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
||||||
|
[productObserver updateWithProducts:self.products];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
|
||||||
|
strf( @"%@\n\n%@", error.localizedDescription,
|
||||||
|
@"Ensure you are online and try logging out and back into iTunes from your device's Settings." )
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
|
||||||
|
cancelTitle:@"OK" otherTitles:nil];
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)requestDidFinish:(SKRequest *)request {
|
||||||
|
|
||||||
|
dbg( @"StoreKit request (%@) finished.", request );
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - SKPaymentTransactionObserver
|
||||||
|
|
||||||
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
|
||||||
|
|
||||||
|
for (SKPaymentTransaction *transaction in transactions) {
|
||||||
|
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
|
||||||
|
switch (transaction.transactionState) {
|
||||||
|
case SKPaymentTransactionStatePurchased: {
|
||||||
|
inf( @"purchased: %@", transaction.payment.productIdentifier );
|
||||||
|
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
|
||||||
|
float currentFuel = [[MPiOSConfig get].developmentFuelRemaining floatValue];
|
||||||
|
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
|
||||||
|
[MPiOSConfig get].developmentFuelRemaining = @(currentFuel + purchasedFuel);
|
||||||
|
if (![MPiOSConfig get].developmentFuelChecked || !currentFuel)
|
||||||
|
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
|
||||||
|
}
|
||||||
|
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
||||||
|
forKey:transaction.payment.productIdentifier];
|
||||||
|
[queue finishTransaction:transaction];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SKPaymentTransactionStateRestored: {
|
||||||
|
inf( @"restored: %@", transaction.payment.productIdentifier );
|
||||||
|
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
|
||||||
|
forKey:transaction.payment.productIdentifier];
|
||||||
|
[queue finishTransaction:transaction];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SKPaymentTransactionStatePurchasing:
|
||||||
|
case SKPaymentTransactionStateDeferred:
|
||||||
|
break;
|
||||||
|
case SKPaymentTransactionStateFailed:
|
||||||
|
err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] );
|
||||||
|
[queue finishTransaction:transaction];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id<MPInAppDelegate> productObserver in self.productObservers)
|
||||||
|
[productObserver updateWithTransaction:transaction];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
|
||||||
|
|
||||||
|
err( @"StoreKit restore failed: %@", [error fullDescription] );
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -9,6 +9,12 @@
|
|||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
|
|
||||||
|
@interface MPAppDelegate_Shared()
|
||||||
|
|
||||||
|
@property(strong, nonatomic) MPKey *key;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation MPAppDelegate_Shared(Key)
|
@implementation MPAppDelegate_Shared(Key)
|
||||||
|
|
||||||
static NSDictionary *keyQuery(MPUserEntity *user) {
|
static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||||
@@ -85,8 +91,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
|
if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
|
||||||
user.keyID = tryKey.keyID;
|
user.keyID = tryKey.keyID;
|
||||||
|
|
||||||
// Migrate existing elements.
|
// Migrate existing sites.
|
||||||
[self migrateElementsForUser:user saveInContext:moc toKey:tryKey];
|
[self migrateSitesForUser:user saveInContext:moc toKey:tryKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,23 +164,23 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)migrateElementsForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
|
- (void)migrateSitesForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
|
||||||
|
|
||||||
if (![user.elements count])
|
if (![user.sites count])
|
||||||
// Nothing to migrate.
|
// Nothing to migrate.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MPKey *recoverKey = newKey;
|
MPKey *recoverKey = newKey;
|
||||||
#ifdef PEARL_UIKIT
|
#ifdef PEARL_UIKIT
|
||||||
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...",
|
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...",
|
||||||
(long)[user.elements count] )];
|
(long)[user.sites count] )];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (MPElementEntity *element in user.elements) {
|
for (MPSiteEntity *site in user.sites) {
|
||||||
if (element.type & MPElementTypeClassStored) {
|
if (site.type & MPSiteTypeClassStored) {
|
||||||
NSString *content;
|
NSString *content;
|
||||||
while (!(content = [element.algorithm storedPasswordForElement:(MPElementStoredEntity *)element usingKey:recoverKey])) {
|
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
|
||||||
// Failed to decrypt element with the current recoveryKey. Ask user for a new one to use.
|
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
|
||||||
__block NSString *masterPassword = nil;
|
__block NSString *masterPassword = nil;
|
||||||
|
|
||||||
#ifdef PEARL_UIKIT
|
#ifdef PEARL_UIKIT
|
||||||
@@ -182,7 +188,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
dispatch_group_enter( recoverPasswordGroup );
|
dispatch_group_enter( recoverPasswordGroup );
|
||||||
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
||||||
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
|
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
|
||||||
element.name )
|
site.name )
|
||||||
viewStyle:UIAlertViewStyleSecureTextInput
|
viewStyle:UIAlertViewStyleSecureTextInput
|
||||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||||
@try {
|
@try {
|
||||||
@@ -202,7 +208,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
// Don't Migrate
|
// Don't Migrate
|
||||||
break;
|
break;
|
||||||
|
|
||||||
recoverKey = [element.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
|
recoverKey = [site.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content)
|
if (!content)
|
||||||
@@ -210,7 +216,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (![recoverKey isEqualToKey:newKey])
|
if (![recoverKey isEqualToKey:newKey])
|
||||||
[element.algorithm savePassword:content toElement:element usingKey:newKey];
|
[site.algorithm savePassword:content toSite:site usingKey:newKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,19 @@
|
|||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
|
|
||||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
@interface MPAppDelegate_Shared : PearlAppDelegate
|
||||||
#else
|
#else
|
||||||
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
|
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@property(strong, nonatomic) MPKey *key;
|
@property(strong, nonatomic, readonly) MPKey *key;
|
||||||
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
|
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
|
||||||
|
|
||||||
+ (instancetype)get;
|
+ (instancetype)get;
|
||||||
|
|
||||||
- (MPUserEntity *)activeUserForMainThread;
|
- (MPUserEntity *)activeUserForMainThread;
|
||||||
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
|
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
|
||||||
- (void)setActiveUser:(MPUserEntity *)activeUser;
|
- (void)setActiveUser:(MPUserEntity *)activeUser;
|
||||||
|
- (void)handleCoordinatorError:(NSError *)error;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -6,10 +6,18 @@
|
|||||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#import <StoreKit/StoreKit.h>
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
|
|
||||||
|
@interface MPAppDelegate_Shared ()
|
||||||
|
|
||||||
|
@property(strong, nonatomic) MPKey *key;
|
||||||
|
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation MPAppDelegate_Shared
|
@implementation MPAppDelegate_Shared
|
||||||
|
|
||||||
+ (MPAppDelegate_Shared *)get {
|
+ (MPAppDelegate_Shared *)get {
|
||||||
@@ -45,9 +53,13 @@
|
|||||||
|
|
||||||
NSError *error;
|
NSError *error;
|
||||||
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
|
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
|
||||||
err(@"Failed to obtain a permanent object ID after setting active user: %@", error);
|
err(@"Failed to obtain a permanent object ID after setting active user: %@", [error fullDescription]);
|
||||||
|
|
||||||
self.activeUserOID = activeUser.objectID;
|
self.activeUserOID = activeUser.objectID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)handleCoordinatorError:(NSError *)error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
|
|||||||
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
|
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
|
||||||
|
- (void)deleteAndResetStore;
|
||||||
|
|
||||||
/** @param completion The block to execute after adding the element, executed from the main thread with the new element in the main MOC. */
|
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
|
||||||
- (void)addElementNamed:(NSString *)siteName completion:(void ( ^ )(MPElementEntity *element, NSManagedObjectContext *context))completion;
|
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
|
||||||
- (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type;
|
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
- (MPImportResult)importSites:(NSString *)importedSitesString
|
||||||
askImportPassword:(NSString *(^)(NSString *userName))importPassword
|
askImportPassword:(NSString *(^)(NSString *userName))importPassword
|
||||||
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
|
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
|
#import "MPGeneratedSiteEntity.h"
|
||||||
|
#import "NSManagedObjectModel+KCOrderedAccessorFix.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
||||||
@@ -33,6 +35,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, PrivateManagedObjectCont
|
|||||||
|
|
||||||
PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext );
|
PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext );
|
||||||
|
|
||||||
|
PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||||
|
|
||||||
#pragma mark - Core Data setup
|
#pragma mark - Core Data setup
|
||||||
|
|
||||||
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
|
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
|
||||||
@@ -142,19 +146,39 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
self.privateManagedObjectContext = nil;
|
self.privateManagedObjectContext = nil;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
// Don't load when the store is corrupted.
|
||||||
|
if ([self.storeCorrupted boolValue])
|
||||||
|
return;
|
||||||
|
|
||||||
// Check if migration is necessary.
|
// Check if migration is necessary.
|
||||||
[self migrateStore];
|
[self migrateStore];
|
||||||
|
|
||||||
// Create a new store coordinator.
|
// Create a new store coordinator.
|
||||||
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:
|
if (!self.persistentStoreCoordinator) {
|
||||||
[NSManagedObjectModel mergedModelFromBundles:nil]];
|
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
|
||||||
|
[model kc_generateOrderedSetAccessors];
|
||||||
|
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
|
||||||
|
}
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
[self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
|
NSURL *localStoreURL = [self localStoreURL];
|
||||||
|
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
|
||||||
|
withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||||
|
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
|
||||||
options:@{
|
options:@{
|
||||||
NSMigratePersistentStoresAutomaticallyOption : @YES,
|
NSMigratePersistentStoresAutomaticallyOption : @YES,
|
||||||
NSInferMappingModelAutomaticallyOption : @YES,
|
NSInferMappingModelAutomaticallyOption : @YES,
|
||||||
STORE_OPTIONS
|
STORE_OPTIONS
|
||||||
} error:&error];
|
} error:&error]) {
|
||||||
|
err( @"Failed to open store: %@", [error fullDescription] );
|
||||||
|
self.storeCorrupted = @YES;
|
||||||
|
[self handleCoordinatorError:error];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.storeCorrupted = @NO;
|
||||||
|
|
||||||
// Create our contexts and observer.
|
// Create our contexts and observer.
|
||||||
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||||
@@ -176,22 +200,19 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp
|
PearlAddNotificationObserver( UIApplicationWillTerminateNotification, UIApp, [NSOperationQueue mainQueue],
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||||
^(NSNotification *note) {
|
|
||||||
[self.mainManagedObjectContext saveToStore];
|
[self.mainManagedObjectContext saveToStore];
|
||||||
}];
|
} );
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
|
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, UIApp, [NSOperationQueue mainQueue],
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||||
^(NSNotification *note) {
|
|
||||||
[self.mainManagedObjectContext saveToStore];
|
[self.mainManagedObjectContext saveToStore];
|
||||||
}];
|
} );
|
||||||
#else
|
#else
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
|
PearlAddNotificationObserver( NSApplicationWillTerminateNotification, NSApp, [NSOperationQueue mainQueue],
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
^(MPAppDelegate_Shared *self, NSNotification *note) {
|
||||||
^(NSNotification *note) {
|
|
||||||
[self.mainManagedObjectContext saveToStore];
|
[self.mainManagedObjectContext saveToStore];
|
||||||
}];
|
} );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Perform a data sanity check on the newly loaded store to find and fix any issues.
|
// Perform a data sanity check on the newly loaded store to find and fix any issues.
|
||||||
@@ -202,6 +223,33 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)deleteAndResetStore {
|
||||||
|
|
||||||
|
@synchronized (self) {
|
||||||
|
// Unregister any existing observers and contexts.
|
||||||
|
if (self.saveObserver)
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver];
|
||||||
|
[self.mainManagedObjectContext performBlockAndWait:^{
|
||||||
|
[self.mainManagedObjectContext reset];
|
||||||
|
self.mainManagedObjectContext = nil;
|
||||||
|
}];
|
||||||
|
[self.privateManagedObjectContext performBlockAndWait:^{
|
||||||
|
[self.privateManagedObjectContext reset];
|
||||||
|
self.privateManagedObjectContext = nil;
|
||||||
|
}];
|
||||||
|
NSError *error = nil;
|
||||||
|
for (NSPersistentStore *store in self.persistentStoreCoordinator.persistentStores) {
|
||||||
|
if (![self.persistentStoreCoordinator removePersistentStore:store error:&error])
|
||||||
|
err( @"Couldn't remove persistence store from coordinator: %@", [error fullDescription] );
|
||||||
|
}
|
||||||
|
self.persistentStoreCoordinator = nil;
|
||||||
|
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
|
||||||
|
err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, [error fullDescription] );
|
||||||
|
|
||||||
|
[self loadStore];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context {
|
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
@@ -214,7 +262,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
fetchRequest.entity = entity;
|
fetchRequest.entity = entity;
|
||||||
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
|
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
|
||||||
if (!objects) {
|
if (!objects) {
|
||||||
err( @"Failed to fetch %@ objects: %@", entity, error );
|
err( @"Failed to fetch %@ objects: %@", entity, [error fullDescription] );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +374,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
|
|
||||||
#pragma mark - Utilities
|
#pragma mark - Utilities
|
||||||
|
|
||||||
- (void)addElementNamed:(NSString *)siteName completion:(void ( ^ )(MPElementEntity *element, NSManagedObjectContext *context))completion {
|
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion {
|
||||||
|
|
||||||
if (![siteName length]) {
|
if (![siteName length]) {
|
||||||
completion( nil, nil );
|
completion( nil, nil );
|
||||||
@@ -341,61 +389,61 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MPElementType type = activeUser.defaultType;
|
MPSiteType type = activeUser.defaultType;
|
||||||
NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
|
NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
|
||||||
|
|
||||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||||
element.name = siteName;
|
site.name = siteName;
|
||||||
element.user = activeUser;
|
site.user = activeUser;
|
||||||
element.type = type;
|
site.type = type;
|
||||||
element.lastUsed = [NSDate date];
|
site.lastUsed = [NSDate date];
|
||||||
element.version = MPAlgorithmDefaultVersion;
|
site.version = MPAlgorithmDefaultVersion;
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error])
|
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] 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 site: %@", [error fullDescription] );
|
||||||
|
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
completion( element, context );
|
completion( site, context );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type {
|
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
|
||||||
|
|
||||||
if (element.type == type)
|
if (site.type == type)
|
||||||
return element;
|
return site;
|
||||||
|
|
||||||
if ([element.algorithm classOfType:type] == element.typeClass) {
|
if ([site.algorithm classOfType:type] == site.typeClass) {
|
||||||
element.type = type;
|
site.type = type;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
// Type requires a different class of element. Recreate the element.
|
// Type requires a different class of site. Recreate the site.
|
||||||
NSString *typeEntityName = [element.algorithm classNameOfType:type];
|
NSString *typeEntityName = [site.algorithm classNameOfType:type];
|
||||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
MPSiteEntity *newSite = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||||
newElement.type = type;
|
newSite.type = type;
|
||||||
newElement.name = element.name;
|
newSite.name = site.name;
|
||||||
newElement.user = element.user;
|
newSite.user = site.user;
|
||||||
newElement.uses = element.uses;
|
newSite.uses = site.uses;
|
||||||
newElement.lastUsed = element.lastUsed;
|
newSite.lastUsed = site.lastUsed;
|
||||||
newElement.version = element.version;
|
newSite.version = site.version;
|
||||||
newElement.loginName = element.loginName;
|
newSite.loginName = site.loginName;
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error])
|
if (![context obtainPermanentIDsForObjects:@[ newSite ] error:&error])
|
||||||
err( @"Failed to obtain a permanent object ID after changing object type: %@", error );
|
err( @"Failed to obtain a permanent object ID after changing object type: %@", [error fullDescription] );
|
||||||
|
|
||||||
[context deleteObject:element];
|
[context deleteObject:site];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
|
||||||
element = newElement;
|
site = newSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
|
||||||
return element;
|
return site;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString
|
- (MPImportResult)importSites:(NSString *)importedSitesString
|
||||||
@@ -431,7 +479,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||||
options:(NSRegularExpressionOptions)0 error:&error];
|
options:(NSRegularExpressionOptions)0 error:&error];
|
||||||
if (error) {
|
if (error) {
|
||||||
err( @"Error loading the header pattern: %@", error );
|
err( @"Error loading the header pattern: %@", [error fullDescription] );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,7 +493,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
options:(NSRegularExpressionOptions)0 error:&error]
|
options:(NSRegularExpressionOptions)0 error:&error]
|
||||||
];
|
];
|
||||||
if (error) {
|
if (error) {
|
||||||
err( @"Error loading the site patterns: %@", error );
|
err( @"Error loading the site patterns: %@", [error fullDescription] );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,9 +508,9 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
NSData *importKeyID = nil;
|
NSData *importKeyID = nil;
|
||||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
NSMutableSet *sitesToDelete = [NSMutableSet set];
|
||||||
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
||||||
NSFetchRequest *elementFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
for (NSString *importedSiteLine in importedSiteLines) {
|
for (NSString *importedSiteLine in importedSiteLines) {
|
||||||
if ([importedSiteLine hasPrefix:@"#"]) {
|
if ([importedSiteLine hasPrefix:@"#"]) {
|
||||||
// Comment or header
|
// Comment or header
|
||||||
@@ -484,10 +532,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
err( @"Invalid header format in line: %@", importedSiteLine );
|
err( @"Invalid header format in line: %@", importedSiteLine );
|
||||||
return MPImportResultMalformedInput;
|
return MPImportResultMalformedInput;
|
||||||
}
|
}
|
||||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
||||||
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
||||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
|
||||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
|
||||||
if ([headerName isEqualToString:@"User Name"]) {
|
if ([headerName isEqualToString:@"User Name"]) {
|
||||||
importUserName = headerValue;
|
importUserName = headerValue;
|
||||||
|
|
||||||
@@ -495,7 +543,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
||||||
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
||||||
if (!users) {
|
if (!users) {
|
||||||
err( @"While looking for user: %@, error: %@", importUserName, error );
|
err( @"While looking for user: %@, error: %@", importUserName, [error fullDescription] );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
if ([users count] > 1) {
|
if ([users count] > 1) {
|
||||||
@@ -579,27 +627,27 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
|
|
||||||
// Find existing site.
|
// Find existing site.
|
||||||
if (user) {
|
if (user) {
|
||||||
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
|
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
|
||||||
NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error];
|
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
|
||||||
if (!existingSites) {
|
if (!existingSites) {
|
||||||
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, error );
|
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, [error fullDescription] );
|
||||||
return MPImportResultInternalError;
|
return MPImportResultInternalError;
|
||||||
}
|
}
|
||||||
if ([existingSites count]) {
|
if ([existingSites count]) {
|
||||||
dbg( @"Existing sites: %@", existingSites );
|
dbg( @"Existing sites: %@", existingSites );
|
||||||
[elementsToDelete addObjectsFromArray:existingSites];
|
[sitesToDelete addObjectsFromArray:existingSites];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[importedSiteElements addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
|
[importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
|
||||||
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
|
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
|
||||||
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
|
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for confirmation to import these sites and the master password of the user.
|
// 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],
|
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
|
||||||
(unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] );
|
(unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
|
||||||
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
|
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
|
||||||
[elementsToDelete count] );
|
[sitesToDelete count] );
|
||||||
if (!userMasterPassword) {
|
if (!userMasterPassword) {
|
||||||
inf( @"Import cancelled." );
|
inf( @"Import cancelled." );
|
||||||
return MPImportResultCancelled;
|
return MPImportResultCancelled;
|
||||||
@@ -615,8 +663,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
|
|
||||||
|
|
||||||
// Delete existing sites.
|
// Delete existing sites.
|
||||||
if (elementsToDelete.count)
|
if (sitesToDelete.count)
|
||||||
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
[sitesToDelete 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];
|
[context deleteObject:obj];
|
||||||
}];
|
}];
|
||||||
@@ -637,10 +685,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Import new sites.
|
// Import new sites.
|
||||||
for (NSArray *siteElements in importedSiteElements) {
|
for (NSArray *siteElements in importedSiteSites) {
|
||||||
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
|
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
|
||||||
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
|
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
|
||||||
MPElementType type = (MPElementType)[siteElements[2] integerValue];
|
MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
|
||||||
NSUInteger version = (unsigned)[siteElements[3] integerValue];
|
NSUInteger version = (unsigned)[siteElements[3] integerValue];
|
||||||
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
|
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
|
||||||
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
|
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
|
||||||
@@ -649,24 +697,24 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
|
|
||||||
// Create new site.
|
// Create new site.
|
||||||
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
|
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
|
||||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
|
||||||
element.name = siteName;
|
site.name = siteName;
|
||||||
element.loginName = loginName;
|
site.loginName = loginName;
|
||||||
element.user = user;
|
site.user = user;
|
||||||
element.type = type;
|
site.type = type;
|
||||||
element.uses = uses;
|
site.uses = uses;
|
||||||
element.lastUsed = lastUsed;
|
site.lastUsed = lastUsed;
|
||||||
element.version = version;
|
site.version = version;
|
||||||
if ([exportContent length]) {
|
if ([exportContent length]) {
|
||||||
if (clearText)
|
if (clearText)
|
||||||
[element.algorithm importClearTextPassword:exportContent intoElement:element usingKey:userKey];
|
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
|
||||||
else
|
else
|
||||||
[element.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
|
[site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||||
}
|
}
|
||||||
if ([element isKindOfClass:[MPElementGeneratedEntity class]] && counter != NSNotFound)
|
if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
|
||||||
((MPElementGeneratedEntity *)element).counter = counter;
|
((MPGeneratedSiteEntity *)site).counter = counter;
|
||||||
|
|
||||||
dbg( @"Created Element: %@", [element debugDescription] );
|
dbg( @"Created Site: %@", [site debugDescription] );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (![context saveToStore])
|
if (![context saveToStore])
|
||||||
@@ -712,27 +760,27 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
|
|||||||
[export appendFormat:@"# used used type name\t name\tpassword\n"];
|
[export appendFormat:@"# used used type name\t name\tpassword\n"];
|
||||||
|
|
||||||
// Sites.
|
// Sites.
|
||||||
for (MPElementEntity *element in activeUser.elements) {
|
for (MPSiteEntity *site in activeUser.sites) {
|
||||||
NSDate *lastUsed = element.lastUsed;
|
NSDate *lastUsed = site.lastUsed;
|
||||||
NSUInteger uses = element.uses;
|
NSUInteger uses = site.uses;
|
||||||
MPElementType type = element.type;
|
MPSiteType type = site.type;
|
||||||
NSUInteger version = element.version;
|
NSUInteger version = site.version;
|
||||||
NSUInteger counter = 0;
|
NSUInteger counter = 0;
|
||||||
NSString *loginName = element.loginName;
|
NSString *loginName = site.loginName;
|
||||||
NSString *siteName = element.name;
|
NSString *siteName = site.name;
|
||||||
NSString *content = nil;
|
NSString *content = nil;
|
||||||
|
|
||||||
// Generated-specific
|
// Generated-specific
|
||||||
if ([element isKindOfClass:[MPElementGeneratedEntity class]])
|
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
counter = ((MPElementGeneratedEntity *)element).counter;
|
counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||||
|
|
||||||
|
|
||||||
// Determine the content to export.
|
// Determine the content to export.
|
||||||
if (!(type & MPElementFeatureDevicePrivate)) {
|
if (!(type & MPSiteFeatureDevicePrivate)) {
|
||||||
if (revealPasswords)
|
if (revealPasswords)
|
||||||
content = [element.algorithm resolvePasswordForElement:element usingKey:self.key];
|
content = [site.algorithm resolvePasswordForSite:site usingKey:self.key];
|
||||||
else if (type & MPElementFeatureExportContent)
|
else if (type & MPSiteFeatureExportContent)
|
||||||
content = [element.algorithm exportPasswordForElement:element usingKey:self.key];
|
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
[export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n",
|
[export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n",
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// MPElementEntity.h
|
|
||||||
// MasterPassword-iOS
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
|
|
||||||
@class MPUserEntity;
|
|
||||||
|
|
||||||
@interface MPElementEntity : NSManagedObject
|
|
||||||
|
|
||||||
//@property (nonatomic, retain) id content; // Hide here, reveal in MPElementStoredEntity
|
|
||||||
@property (nonatomic, retain) NSDate * lastUsed;
|
|
||||||
@property (nonatomic, retain) NSString * loginName;
|
|
||||||
@property (nonatomic, retain) NSString * name;
|
|
||||||
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
|
|
||||||
@property (nonatomic, retain) NSNumber * type_;
|
|
||||||
@property (nonatomic, retain) NSNumber * uses_;
|
|
||||||
@property (nonatomic, retain) NSNumber * version_;
|
|
||||||
@property (nonatomic, retain) NSNumber * loginGenerated_;
|
|
||||||
@property (nonatomic, retain) MPUserEntity *user;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//
|
|
||||||
// MPElementGeneratedEntity.m
|
|
||||||
// MasterPassword-iOS
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPElementGeneratedEntity.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MPElementGeneratedEntity
|
|
||||||
|
|
||||||
@dynamic counter_;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// MPElementStoredEntity.h
|
|
||||||
// MasterPassword-iOS
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreData/CoreData.h>
|
|
||||||
#import "MPElementEntity.h"
|
|
||||||
|
|
||||||
|
|
||||||
@interface MPElementStoredEntity : MPElementEntity
|
|
||||||
|
|
||||||
@property (nonatomic, retain) NSData * contentObject;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//
|
|
||||||
// MPElementStoredEntity.m
|
|
||||||
// MasterPassword-iOS
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPElementStoredEntity.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MPElementStoredEntity
|
|
||||||
|
|
||||||
@dynamic contentObject;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// MPElementEntities.h
|
// MPEntities.h
|
||||||
// MasterPassword-iOS
|
// MasterPassword-iOS
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 31/05/12.
|
// Created by Maarten Billemont on 31/05/12.
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "MPElementEntity.h"
|
#import "MPSiteEntity.h"
|
||||||
#import "MPElementStoredEntity.h"
|
#import "MPStoredSiteEntity.h"
|
||||||
#import "MPElementGeneratedEntity.h"
|
#import "MPGeneratedSiteEntity.h"
|
||||||
#import "MPUserEntity.h"
|
#import "MPUserEntity.h"
|
||||||
#import "MPAlgorithm.h"
|
#import "MPAlgorithm.h"
|
||||||
#import "MPFixable.h"
|
#import "MPFixable.h"
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MPElementEntity(MP)<MPFixable>
|
@interface MPSiteEntity(MP)<MPFixable>
|
||||||
|
|
||||||
@property(assign) BOOL loginGenerated;
|
@property(assign) BOOL loginGenerated;
|
||||||
@property(assign) MPElementType type;
|
@property(assign) MPSiteType type;
|
||||||
@property(readonly) NSString *typeName;
|
@property(readonly) NSString *typeName;
|
||||||
@property(readonly) NSString *typeShortName;
|
@property(readonly) NSString *typeShortName;
|
||||||
@property(readonly) NSString *typeClassName;
|
@property(readonly) NSString *typeClassName;
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
@property(readonly) id<MPAlgorithm> algorithm;
|
@property(readonly) id<MPAlgorithm> algorithm;
|
||||||
|
|
||||||
- (NSUInteger)use;
|
- (NSUInteger)use;
|
||||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
|
||||||
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
|
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
|
||||||
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
|
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
|
||||||
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MPElementGeneratedEntity(MP)
|
@interface MPGeneratedSiteEntity(MP)
|
||||||
|
|
||||||
@property(assign) NSUInteger counter;
|
@property(assign) NSUInteger counter;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
@property(assign) NSUInteger avatar;
|
@property(assign) NSUInteger avatar;
|
||||||
@property(assign) BOOL saveKey;
|
@property(assign) BOOL saveKey;
|
||||||
@property(assign) MPElementType defaultType;
|
@property(assign) MPSiteType defaultType;
|
||||||
@property(readonly) NSString *userID;
|
@property(readonly) NSString *userID;
|
||||||
|
|
||||||
+ (NSString *)idFor:(NSString *)userName;
|
+ (NSString *)idFor:(NSString *)userName;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// MPElementEntities.m
|
// MPEntities.m
|
||||||
// MasterPassword-iOS
|
// MasterPassword-iOS
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 31/05/12.
|
// Created by Maarten Billemont on 31/05/12.
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
|
||||||
|
|
||||||
@implementation NSManagedObjectContext(MP)
|
@implementation NSManagedObjectContext(MP)
|
||||||
|
|
||||||
@@ -34,16 +33,16 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPElementEntity(MP)
|
@implementation MPSiteEntity(MP)
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
return MPFixableResultNoProblems;
|
return MPFixableResultNoProblems;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)type {
|
- (MPSiteType)type {
|
||||||
|
|
||||||
return (MPElementType)[self.type_ unsignedIntegerValue];
|
return (MPSiteType)[self.type_ unsignedIntegerValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
|
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
return [self.loginGenerated_ boolValue];
|
return [self.loginGenerated_ boolValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setType:(MPElementType)aType {
|
- (void)setType:(MPSiteType)aType {
|
||||||
|
|
||||||
self.type_ = @(aType);
|
self.type_ = @(aType);
|
||||||
}
|
}
|
||||||
@@ -135,50 +134,52 @@
|
|||||||
self.loginName, self.requiresExplicitMigration );
|
self.loginName, self.requiresExplicitMigration );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {
|
||||||
|
|
||||||
while (self.version < MPAlgorithmDefaultVersion)
|
while (self.version < MPAlgorithmDefaultVersion) {
|
||||||
if ([MPAlgorithmForVersion( self.version + 1 ) migrateElement:self explicit:explicit])
|
NSUInteger toVersion = self.version + 1;
|
||||||
inf( @"%@ migration to version: %ld succeeded for element: %@",
|
if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) {
|
||||||
explicit? @"Explicit": @"Automatic", (long)self.version + 1, self );
|
wrn( @"%@ migration to version: %ld failed for site: %@",
|
||||||
else {
|
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
|
||||||
wrn( @"%@ migration to version: %ld failed for element: %@",
|
|
||||||
explicit? @"Explicit": @"Automatic", (long)self.version + 1, self );
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inf( @"%@ migration to version: %ld succeeded for site: %@",
|
||||||
|
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
|
||||||
|
}
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolveLoginUsingKey:(MPKey *)key {
|
- (NSString *)resolveLoginUsingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self.algorithm resolveLoginForElement:self usingKey:key];
|
return [self.algorithm resolveLoginForSite:self usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)resolvePasswordUsingKey:(MPKey *)key {
|
- (NSString *)resolvePasswordUsingKey:(MPKey *)key {
|
||||||
|
|
||||||
return [self.algorithm resolvePasswordForElement:self usingKey:key];
|
return [self.algorithm resolvePasswordForSite:self usingKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
||||||
|
|
||||||
[self.algorithm resolveLoginForElement:self usingKey:key result:result];
|
[self.algorithm resolveLoginForSite:self usingKey:key result:result];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
|
||||||
|
|
||||||
[self.algorithm resolvePasswordForElement:self usingKey:key result:result];
|
[self.algorithm resolvePasswordForSite:self usingKey:key result:result];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPElementGeneratedEntity(MP)
|
@implementation MPGeneratedSiteEntity(MP)
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
|
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
|
||||||
|
|
||||||
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
||||||
// Invalid self.type
|
// Invalid self.type
|
||||||
result = MPApplyFix( result, ^MPFixableResult {
|
result = MPApplyFix( result, ^MPFixableResult {
|
||||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||||
@@ -186,18 +187,18 @@
|
|||||||
self.type = self.user.defaultType;
|
self.type = self.user.defaultType;
|
||||||
return MPFixableResultProblemsFixed;
|
return MPFixableResultProblemsFixed;
|
||||||
} );
|
} );
|
||||||
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
|
||||||
// Invalid self.user.defaultType
|
// Invalid self.user.defaultType
|
||||||
result = MPApplyFix( result, ^MPFixableResult {
|
result = MPApplyFix( result, ^MPFixableResult {
|
||||||
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
|
||||||
self.name, self.user.name, (long)self.type, (long)MPElementTypeGeneratedLong );
|
self.name, self.user.name, (long)self.type, (long)MPSiteTypeGeneratedLong );
|
||||||
self.type = MPElementTypeGeneratedLong;
|
self.type = MPSiteTypeGeneratedLong;
|
||||||
return MPFixableResultProblemsFixed;
|
return MPFixableResultProblemsFixed;
|
||||||
} );
|
} );
|
||||||
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
|
||||||
// Mismatch between self.type and self.class
|
// Mismatch between self.type and self.class
|
||||||
result = MPApplyFix( result, ^MPFixableResult {
|
result = MPApplyFix( result, ^MPFixableResult {
|
||||||
for (MPElementType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
|
for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
|
||||||
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
|
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
|
||||||
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
|
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.name, self.user.name, (long)self.type, self.class, (long)newType );
|
||||||
@@ -225,7 +226,7 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPElementStoredEntity(MP)
|
@implementation MPStoredSiteEntity(MP)
|
||||||
|
|
||||||
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||||
if (key && [[MPAppDelegate_Shared get] activeUserInContext:context] == self.user) {
|
if (key && [[MPAppDelegate_Shared get] activeUserInContext:context] == self.user) {
|
||||||
wrn( @"Content object not encrypted for: %@ of %@. Will re-encrypt.", self.name, self.user.name );
|
wrn( @"Content object not encrypted for: %@ of %@. Will re-encrypt.", self.name, self.user.name );
|
||||||
[self.algorithm savePassword:[self.contentObject description] toElement:self usingKey:key];
|
[self.algorithm savePassword:[self.contentObject description] toSite:self usingKey:key];
|
||||||
return MPFixableResultProblemsFixed;
|
return MPFixableResultProblemsFixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,12 +272,12 @@
|
|||||||
self.saveKey_ = @(aSaveKey);
|
self.saveKey_ = @(aSaveKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)defaultType {
|
- (MPSiteType)defaultType {
|
||||||
|
|
||||||
return (MPElementType)[self.defaultType_ unsignedIntegerValue]?: MPElementTypeGeneratedLong;
|
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDefaultType:(MPElementType)aDefaultType {
|
- (void)setDefaultType:(MPSiteType)aDefaultType {
|
||||||
|
|
||||||
self.defaultType_ = @(aDefaultType);
|
self.defaultType_ = @(aDefaultType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
//
|
//
|
||||||
// MPElementGeneratedEntity.h
|
// MPGeneratedSiteEntity.h
|
||||||
// MasterPassword-iOS
|
// MasterPassword-Mac
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <CoreData/CoreData.h>
|
#import <CoreData/CoreData.h>
|
||||||
#import "MPElementEntity.h"
|
#import "MPSiteEntity.h"
|
||||||
|
|
||||||
|
|
||||||
@interface MPElementGeneratedEntity : MPElementEntity
|
@interface MPGeneratedSiteEntity : MPSiteEntity
|
||||||
|
|
||||||
@property (nonatomic, retain) NSNumber * counter_;
|
@property (nonatomic, retain) NSNumber * counter_;
|
||||||
|
|
||||||
16
MasterPassword/ObjC/MPGeneratedSiteEntity.m
Normal file
16
MasterPassword/ObjC/MPGeneratedSiteEntity.m
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// MPGeneratedSiteEntity.m
|
||||||
|
// MasterPassword-Mac
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPGeneratedSiteEntity.h"
|
||||||
|
|
||||||
|
|
||||||
|
@implementation MPGeneratedSiteEntity
|
||||||
|
|
||||||
|
@dynamic counter_;
|
||||||
|
|
||||||
|
@end
|
||||||
41
MasterPassword/ObjC/MPSiteEntity.h
Normal file
41
MasterPassword/ObjC/MPSiteEntity.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// MPSiteEntity.h
|
||||||
|
// MasterPassword-Mac
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
|
@class MPSiteQuestionEntity, MPUserEntity;
|
||||||
|
|
||||||
|
@interface MPSiteEntity : NSManagedObject
|
||||||
|
|
||||||
|
//@property (nonatomic, retain) id content; // Hide here, reveal in MPStoredSiteEntity
|
||||||
|
@property (nonatomic, retain) NSDate * lastUsed;
|
||||||
|
@property (nonatomic, retain) NSNumber * loginGenerated_;
|
||||||
|
@property (nonatomic, retain) NSString * loginName;
|
||||||
|
@property (nonatomic, retain) NSString * name;
|
||||||
|
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
|
||||||
|
@property (nonatomic, retain) NSNumber * type_;
|
||||||
|
@property (nonatomic, retain) NSNumber * uses_;
|
||||||
|
@property (nonatomic, retain) NSNumber * version_;
|
||||||
|
@property (nonatomic, retain) NSOrderedSet *questions;
|
||||||
|
@property (nonatomic, retain) MPUserEntity *user;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPSiteEntity (CoreDataGeneratedAccessors)
|
||||||
|
|
||||||
|
- (void)insertObject:(MPSiteQuestionEntity *)value inQuestionsAtIndex:(NSUInteger)idx;
|
||||||
|
- (void)removeObjectFromQuestionsAtIndex:(NSUInteger)idx;
|
||||||
|
- (void)insertQuestions:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
|
||||||
|
- (void)removeQuestionsAtIndexes:(NSIndexSet *)indexes;
|
||||||
|
- (void)replaceObjectInQuestionsAtIndex:(NSUInteger)idx withObject:(MPSiteQuestionEntity *)value;
|
||||||
|
- (void)replaceQuestionsAtIndexes:(NSIndexSet *)indexes withQuestions:(NSArray *)values;
|
||||||
|
- (void)addQuestionsObject:(MPSiteQuestionEntity *)value;
|
||||||
|
- (void)removeQuestionsObject:(MPSiteQuestionEntity *)value;
|
||||||
|
- (void)addQuestions:(NSOrderedSet *)values;
|
||||||
|
- (void)removeQuestions:(NSOrderedSet *)values;
|
||||||
|
@end
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
//
|
//
|
||||||
// MPElementEntity.m
|
// MPSiteEntity.m
|
||||||
// MasterPassword-iOS
|
// MasterPassword-Mac
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPElementEntity.h"
|
#import "MPSiteEntity.h"
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
#import "MPUserEntity.h"
|
#import "MPUserEntity.h"
|
||||||
|
|
||||||
|
|
||||||
@implementation MPElementEntity
|
@implementation MPSiteEntity
|
||||||
|
|
||||||
//@dynamic content;
|
//@dynamic content;
|
||||||
@dynamic lastUsed;
|
@dynamic lastUsed;
|
||||||
|
@dynamic loginGenerated_;
|
||||||
@dynamic loginName;
|
@dynamic loginName;
|
||||||
@dynamic name;
|
@dynamic name;
|
||||||
@dynamic requiresExplicitMigration_;
|
@dynamic requiresExplicitMigration_;
|
||||||
@dynamic type_;
|
@dynamic type_;
|
||||||
@dynamic uses_;
|
@dynamic uses_;
|
||||||
@dynamic version_;
|
@dynamic version_;
|
||||||
@dynamic loginGenerated_;
|
@dynamic questions;
|
||||||
@dynamic user;
|
@dynamic user;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
19
MasterPassword/ObjC/MPSiteQuestionEntity.h
Normal file
19
MasterPassword/ObjC/MPSiteQuestionEntity.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// MPSiteQuestionEntity.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-27.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
|
@class MPSiteEntity;
|
||||||
|
|
||||||
|
@interface MPSiteQuestionEntity : NSManagedObject
|
||||||
|
|
||||||
|
@property (nonatomic, retain) NSString * keyword;
|
||||||
|
@property (nonatomic, retain) MPSiteEntity *site;
|
||||||
|
|
||||||
|
@end
|
||||||
18
MasterPassword/ObjC/MPSiteQuestionEntity.m
Normal file
18
MasterPassword/ObjC/MPSiteQuestionEntity.m
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// MPSiteQuestionEntity.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-27.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
|
#import "MPSiteEntity.h"
|
||||||
|
|
||||||
|
|
||||||
|
@implementation MPSiteQuestionEntity
|
||||||
|
|
||||||
|
@dynamic keyword;
|
||||||
|
@dynamic site;
|
||||||
|
|
||||||
|
@end
|
||||||
18
MasterPassword/ObjC/MPStoredSiteEntity.h
Normal file
18
MasterPassword/ObjC/MPStoredSiteEntity.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// MPStoredSiteEntity.h
|
||||||
|
// MasterPassword-Mac
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreData/CoreData.h>
|
||||||
|
#import "MPSiteEntity.h"
|
||||||
|
|
||||||
|
|
||||||
|
@interface MPStoredSiteEntity : MPSiteEntity
|
||||||
|
|
||||||
|
@property (nonatomic, retain) NSData *contentObject;
|
||||||
|
|
||||||
|
@end
|
||||||
16
MasterPassword/ObjC/MPStoredSiteEntity.m
Normal file
16
MasterPassword/ObjC/MPStoredSiteEntity.m
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// MPStoredSiteEntity.m
|
||||||
|
// MasterPassword-Mac
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPStoredSiteEntity.h"
|
||||||
|
|
||||||
|
|
||||||
|
@implementation MPStoredSiteEntity
|
||||||
|
|
||||||
|
@dynamic contentObject;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -8,38 +8,41 @@
|
|||||||
|
|
||||||
#import "MPKey.h"
|
#import "MPKey.h"
|
||||||
|
|
||||||
typedef NS_ENUM(NSUInteger, MPElementTypeClass) {
|
typedef NS_ENUM( NSUInteger, MPSiteTypeClass ) {
|
||||||
/** Generate the password. */
|
/** Generate the password. */
|
||||||
MPElementTypeClassGenerated = 1 << 4,
|
MPSiteTypeClassGenerated = 1 << 4,
|
||||||
/** Store the password. */
|
/** Store the password. */
|
||||||
MPElementTypeClassStored = 1 << 5,
|
MPSiteTypeClassStored = 1 << 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(NSUInteger, MPElementVariant) {
|
typedef NS_ENUM( NSUInteger, MPSiteVariant ) {
|
||||||
/** Generate the password. */
|
/** Generate the password. */
|
||||||
MPElementVariantPassword,
|
MPSiteVariantPassword,
|
||||||
/** Generate the login name. */
|
/** Generate the login name. */
|
||||||
MPElementVariantLogin,
|
MPSiteVariantLogin,
|
||||||
|
/** Generate a security answer. */
|
||||||
|
MPSiteVariantAnswer,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(NSUInteger, MPElementFeature) {
|
typedef NS_ENUM( NSUInteger, MPSiteFeature ) {
|
||||||
/** Export the key-protected content data. */
|
/** Export the key-protected content data. */
|
||||||
MPElementFeatureExportContent = 1 << 10,
|
MPSiteFeatureExportContent = 1 << 10,
|
||||||
/** Never export content. */
|
/** Never export content. */
|
||||||
MPElementFeatureDevicePrivate = 1 << 11,
|
MPSiteFeatureDevicePrivate = 1 << 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(NSUInteger, MPElementType) {
|
typedef NS_ENUM(NSUInteger, MPSiteType) {
|
||||||
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedMaximum = 0x0 | MPSiteTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedLong = 0x1 | MPSiteTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedMedium = 0x2 | MPSiteTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedBasic = 0x4 | MPSiteTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedShort = 0x3 | MPSiteTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedPIN = 0x5 | MPSiteTypeClassGenerated | 0x0,
|
||||||
MPElementTypeGeneratedName = 0xF | MPElementTypeClassGenerated | 0x0,
|
MPSiteTypeGeneratedName = 0xE | MPSiteTypeClassGenerated | 0x0,
|
||||||
|
MPSiteTypeGeneratedPhrase = 0xF | MPSiteTypeClassGenerated | 0x0,
|
||||||
|
|
||||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
MPSiteTypeStoredPersonal = 0x0 | MPSiteTypeClassStored | MPSiteFeatureExportContent,
|
||||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
MPSiteTypeStoredDevicePrivate = 0x1 | MPSiteTypeClassStored | MPSiteFeatureDevicePrivate,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MPErrorDomain @"MPErrorDomain"
|
#define MPErrorDomain @"MPErrorDomain"
|
||||||
@@ -52,7 +55,7 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
|
|||||||
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
|
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
|
||||||
#define MPCheckpointEditLoginName @"MPCheckpointEditLoginName"
|
#define MPCheckpointEditLoginName @"MPCheckpointEditLoginName"
|
||||||
#define MPCheckpointUseType @"MPCheckpointUseType"
|
#define MPCheckpointUseType @"MPCheckpointUseType"
|
||||||
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
|
#define MPCheckpointDeleteSite @"MPCheckpointDeleteSite"
|
||||||
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
|
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
|
||||||
#define MPCheckpointShowSetup @"MPCheckpointShowSetup"
|
#define MPCheckpointShowSetup @"MPCheckpointShowSetup"
|
||||||
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
|
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
|
||||||
@@ -76,7 +79,7 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
|
|||||||
#define MPSignedInNotification @"MPSignedInNotification"
|
#define MPSignedInNotification @"MPSignedInNotification"
|
||||||
#define MPSignedOutNotification @"MPSignedOutNotification"
|
#define MPSignedOutNotification @"MPSignedOutNotification"
|
||||||
#define MPKeyForgottenNotification @"MPKeyForgottenNotification"
|
#define MPKeyForgottenNotification @"MPKeyForgottenNotification"
|
||||||
#define MPElementUpdatedNotification @"MPElementUpdatedNotification"
|
#define MPSiteUpdatedNotification @"MPSiteUpdatedNotification"
|
||||||
#define MPCheckConfigNotification @"MPCheckConfigNotification"
|
#define MPCheckConfigNotification @"MPCheckConfigNotification"
|
||||||
#define MPSitesImportedNotification @"MPSitesImportedNotification"
|
#define MPSitesImportedNotification @"MPSitesImportedNotification"
|
||||||
#define MPFoundInconsistenciesNotification @"MPFoundInconsistenciesNotification"
|
#define MPFoundInconsistenciesNotification @"MPFoundInconsistenciesNotification"
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
//
|
//
|
||||||
// MPUserEntity.h
|
// MPUserEntity.h
|
||||||
// MasterPassword-iOS
|
// MasterPassword-Mac
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <CoreData/CoreData.h>
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
@class MPElementEntity;
|
@class MPSiteEntity;
|
||||||
|
|
||||||
@interface MPUserEntity : NSManagedObject
|
@interface MPUserEntity : NSManagedObject
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@
|
|||||||
@property (nonatomic, retain) NSDate * lastUsed;
|
@property (nonatomic, retain) NSDate * lastUsed;
|
||||||
@property (nonatomic, retain) NSString * name;
|
@property (nonatomic, retain) NSString * name;
|
||||||
@property (nonatomic, retain) NSNumber * saveKey_;
|
@property (nonatomic, retain) NSNumber * saveKey_;
|
||||||
@property (nonatomic, retain) NSSet *elements;
|
@property (nonatomic, retain) NSSet *sites;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MPUserEntity (CoreDataGeneratedAccessors)
|
@interface MPUserEntity (CoreDataGeneratedAccessors)
|
||||||
|
|
||||||
- (void)addElementsObject:(MPElementEntity *)value;
|
- (void)addSitesObject:(MPSiteEntity *)value;
|
||||||
- (void)removeElementsObject:(MPElementEntity *)value;
|
- (void)removeSitesObject:(MPSiteEntity *)value;
|
||||||
- (void)addElements:(NSSet *)values;
|
- (void)addSites:(NSSet *)values;
|
||||||
- (void)removeElements:(NSSet *)values;
|
- (void)removeSites:(NSSet *)values;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
//
|
//
|
||||||
// MPUserEntity.m
|
// MPUserEntity.m
|
||||||
// MasterPassword-iOS
|
// MasterPassword-Mac
|
||||||
//
|
//
|
||||||
// Created by Maarten Billemont on 2014-09-14.
|
// Created by Maarten Billemont on 2014-09-21.
|
||||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPUserEntity.h"
|
#import "MPUserEntity.h"
|
||||||
#import "MPElementEntity.h"
|
#import "MPSiteEntity.h"
|
||||||
|
|
||||||
|
|
||||||
@implementation MPUserEntity
|
@implementation MPUserEntity
|
||||||
@@ -18,6 +18,6 @@
|
|||||||
@dynamic lastUsed;
|
@dynamic lastUsed;
|
||||||
@dynamic name;
|
@dynamic name;
|
||||||
@dynamic saveKey_;
|
@dynamic saveKey_;
|
||||||
@dynamic elements;
|
@dynamic sites;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||||
returningResponse:&response error:&error];
|
returningResponse:&response error:&error];
|
||||||
if (error)
|
if (error)
|
||||||
err( @"While reading imported sites from %@: %@", url, error );
|
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
|
||||||
if (!importedSitesData)
|
if (!importedSitesData)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -346,7 +346,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
[moc saveToStore];
|
[moc saveToStore];
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
|
if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
|
||||||
err( @"Failed to obtain permanent object ID for new user: %@", error );
|
err( @"Failed to obtain permanent object ID for new user: %@", [error fullDescription] );
|
||||||
|
|
||||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||||
[self updateUsers];
|
[self updateUsers];
|
||||||
@@ -510,7 +510,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
|||||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||||
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
||||||
if (!users)
|
if (!users)
|
||||||
err( @"Failed to load users: %@", error );
|
err( @"Failed to load users: %@", [error fullDescription] );
|
||||||
|
|
||||||
if (![users count]) {
|
if (![users count]) {
|
||||||
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
|
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
|
||||||
|
|||||||
@@ -17,26 +17,26 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "MPElementModel.h"
|
#import "MPSiteModel.h"
|
||||||
#import "MPElementsTableView.h"
|
#import "MPSitesTableView.h"
|
||||||
|
|
||||||
@class MPMacAppDelegate;
|
@class MPMacAppDelegate;
|
||||||
|
|
||||||
@interface MPPasswordWindowController : NSWindowController<NSTextViewDelegate, NSTextFieldDelegate, NSTableViewDataSource, NSTableViewDelegate>
|
@interface MPPasswordWindowController : NSWindowController<NSTextViewDelegate, NSTextFieldDelegate, NSTableViewDataSource, NSTableViewDelegate>
|
||||||
|
|
||||||
@property(nonatomic) NSMutableArray *elements;
|
@property(nonatomic) NSMutableArray *sites;
|
||||||
@property(nonatomic) NSString *masterPassword;
|
@property(nonatomic) NSString *masterPassword;
|
||||||
@property(nonatomic) BOOL alternatePressed;
|
@property(nonatomic) BOOL alternatePressed;
|
||||||
@property(nonatomic) BOOL locked;
|
@property(nonatomic) BOOL locked;
|
||||||
@property(nonatomic) BOOL newUser;
|
@property(nonatomic) BOOL newUser;
|
||||||
|
|
||||||
@property(nonatomic, weak) IBOutlet NSArrayController *elementsController;
|
@property(nonatomic, weak) IBOutlet NSArrayController *sitesController;
|
||||||
@property(nonatomic, weak) IBOutlet NSImageView *blurView;
|
@property(nonatomic, weak) IBOutlet NSImageView *blurView;
|
||||||
@property(nonatomic, weak) IBOutlet NSTextField *inputLabel;
|
@property(nonatomic, weak) IBOutlet NSTextField *inputLabel;
|
||||||
@property(nonatomic, weak) IBOutlet NSTextField *securePasswordField;
|
@property(nonatomic, weak) IBOutlet NSTextField *securePasswordField;
|
||||||
@property(nonatomic, weak) IBOutlet NSTextField *revealPasswordField;
|
@property(nonatomic, weak) IBOutlet NSTextField *revealPasswordField;
|
||||||
@property(nonatomic, weak) IBOutlet NSSearchField *siteField;
|
@property(nonatomic, weak) IBOutlet NSSearchField *siteField;
|
||||||
@property(nonatomic, weak) IBOutlet MPElementsTableView *siteTable;
|
@property(nonatomic, weak) IBOutlet MPSitesTableView *siteTable;
|
||||||
@property(nonatomic, weak) IBOutlet NSProgressIndicator *progressView;
|
@property(nonatomic, weak) IBOutlet NSProgressIndicator *progressView;
|
||||||
|
|
||||||
@property(nonatomic, strong) IBOutlet NSBox *passwordTypesBox;
|
@property(nonatomic, strong) IBOutlet NSBox *passwordTypesBox;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#import "MPPasswordWindowController.h"
|
#import "MPPasswordWindowController.h"
|
||||||
#import "MPMacAppDelegate.h"
|
#import "MPMacAppDelegate.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "MPElementModel.h"
|
#import "MPSiteModel.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
#import "PearlProfiler.h"
|
#import "PearlProfiler.h"
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||||
[self updateUser];
|
[self updateUser];
|
||||||
}];
|
}];
|
||||||
[self observeKeyPath:@"elementsController.selection"
|
[self observeKeyPath:@"sitesController.selection"
|
||||||
withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||||
[_self updateSelection];
|
[_self updateSelection];
|
||||||
}];
|
}];
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
|
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
|
||||||
if (alternatePressed != self.alternatePressed) {
|
if (alternatePressed != self.alternatePressed) {
|
||||||
self.alternatePressed = alternatePressed;
|
self.alternatePressed = alternatePressed;
|
||||||
[self.selectedElement updateContent];
|
[self.selectedSite updateContent];
|
||||||
|
|
||||||
if (self.locked) {
|
if (self.locked) {
|
||||||
NSTextField *passwordField = self.securePasswordField;
|
NSTextField *passwordField = self.securePasswordField;
|
||||||
@@ -169,9 +169,9 @@
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)doSearchElements:(id)sender {
|
- (IBAction)doSearchSites:(id)sender {
|
||||||
|
|
||||||
[self updateElements];
|
[self updateSites];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - NSTextViewDelegate
|
#pragma mark - NSTextViewDelegate
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
|
|
||||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
|
||||||
|
|
||||||
return (NSInteger)[self.elements count];
|
return (NSInteger)[self.sites count];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - NSTableViewDelegate
|
#pragma mark - NSTableViewDelegate
|
||||||
@@ -229,9 +229,10 @@
|
|||||||
switch (returnCode) {
|
switch (returnCode) {
|
||||||
case NSAlertFirstButtonReturn: {
|
case NSAlertFirstButtonReturn: {
|
||||||
// "Create" button.
|
// "Create" button.
|
||||||
[[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
|
[[MPMacAppDelegate get] addSiteNamed:[self.siteField stringValue] completion:
|
||||||
if (element)
|
^(MPSiteEntity *site, NSManagedObjectContext *context) {
|
||||||
PearlMainQueue( ^{ [self updateElements]; } );
|
if (site)
|
||||||
|
PearlMainQueue( ^{ [self updateSites]; } );
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -243,11 +244,11 @@
|
|||||||
switch (returnCode) {
|
switch (returnCode) {
|
||||||
case NSAlertFirstButtonReturn: {
|
case NSAlertFirstButtonReturn: {
|
||||||
// "Save" button.
|
// "Save" button.
|
||||||
MPElementType type = (MPElementType)[self.passwordTypesMatrix.selectedCell tag];
|
MPSiteType type = (MPSiteType)[self.passwordTypesMatrix.selectedCell tag];
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *entity = [[MPMacAppDelegate get] changeElement:[self.selectedElement entityInContext:context]
|
MPSiteEntity *entity = [[MPMacAppDelegate get] changeSite:[self.selectedSite entityInContext:context]
|
||||||
saveInContext:context toType:type];
|
saveInContext:context toType:type];
|
||||||
if ([entity isKindOfClass:[MPElementStoredEntity class]] && ![(MPElementStoredEntity *)entity contentObject].length)
|
if ([entity isKindOfClass:[MPStoredSiteEntity class]] && ![(MPStoredSiteEntity *)entity contentObject].length)
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
[self changePassword:nil];
|
[self changePassword:nil];
|
||||||
} );
|
} );
|
||||||
@@ -264,7 +265,7 @@
|
|||||||
// "Save" button.
|
// "Save" button.
|
||||||
NSString *loginName = [(NSSecureTextField *)alert.accessoryView stringValue];
|
NSString *loginName = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *entity = [self.selectedElement entityInContext:context];
|
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
|
||||||
entity.loginName = loginName;
|
entity.loginName = loginName;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
}];
|
}];
|
||||||
@@ -280,8 +281,8 @@
|
|||||||
// "Save" button.
|
// "Save" button.
|
||||||
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
|
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *entity = [self.selectedElement entityInContext:context];
|
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
|
||||||
[entity.algorithm savePassword:password toElement:entity usingKey:[MPMacAppDelegate get].key];
|
[entity.algorithm savePassword:password toSite:entity usingKey:[MPMacAppDelegate get].key];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
@@ -295,7 +296,7 @@
|
|||||||
case NSAlertFirstButtonReturn: {
|
case NSAlertFirstButtonReturn: {
|
||||||
// "Delete" button.
|
// "Delete" button.
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[context deleteObject:[self.selectedElement entityInContext:context]];
|
[context deleteObject:[self.selectedSite entityInContext:context]];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
@@ -313,19 +314,19 @@
|
|||||||
return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @"";
|
return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @"";
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index {
|
- (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index {
|
||||||
|
|
||||||
[self.elements insertObject:model atIndex:index];
|
[self.sites insertObject:model atIndex:index];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeObjectFromElementsAtIndex:(NSUInteger)index {
|
- (void)removeObjectFromSitesAtIndex:(NSUInteger)index {
|
||||||
|
|
||||||
[self.elements removeObjectAtIndex:index];
|
[self.sites removeObjectAtIndex:index];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementModel *)selectedElement {
|
- (MPSiteModel *)selectedSite {
|
||||||
|
|
||||||
return [self.elementsController.selectedObjects firstObject];
|
return [self.sitesController.selectedObjects firstObject];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
@@ -336,13 +337,13 @@
|
|||||||
[[MPMacAppDelegate get] showPopup:sender];
|
[[MPMacAppDelegate get] showPopup:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)deleteElement:(id)sender {
|
- (IBAction)deleteSite:(id)sender {
|
||||||
|
|
||||||
NSAlert *alert = [NSAlert new];
|
NSAlert *alert = [NSAlert new];
|
||||||
[alert addButtonWithTitle:@"Delete"];
|
[alert addButtonWithTitle:@"Delete"];
|
||||||
[alert addButtonWithTitle:@"Cancel"];
|
[alert addButtonWithTitle:@"Cancel"];
|
||||||
[alert setMessageText:@"Delete Site?"];
|
[alert setMessageText:@"Delete Site?"];
|
||||||
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedElement.siteName )];
|
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.siteName )];
|
||||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||||
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
|
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
|
||||||
}
|
}
|
||||||
@@ -353,9 +354,9 @@
|
|||||||
[alert addButtonWithTitle:@"Save"];
|
[alert addButtonWithTitle:@"Save"];
|
||||||
[alert addButtonWithTitle:@"Cancel"];
|
[alert addButtonWithTitle:@"Cancel"];
|
||||||
[alert setMessageText:@"Change Login Name"];
|
[alert setMessageText:@"Change Login Name"];
|
||||||
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedElement.siteName )];
|
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.siteName )];
|
||||||
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||||
loginField.stringValue = self.selectedElement.loginName?: @"";
|
loginField.stringValue = self.selectedSite.loginName?: @"";
|
||||||
[loginField selectText:self];
|
[loginField selectText:self];
|
||||||
[alert setAccessoryView:loginField];
|
[alert setAccessoryView:loginField];
|
||||||
[alert layout];
|
[alert layout];
|
||||||
@@ -380,14 +381,14 @@
|
|||||||
|
|
||||||
- (IBAction)changePassword:(id)sender {
|
- (IBAction)changePassword:(id)sender {
|
||||||
|
|
||||||
if (!self.selectedElement.stored)
|
if (!self.selectedSite.stored)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
NSAlert *alert = [NSAlert new];
|
NSAlert *alert = [NSAlert new];
|
||||||
[alert addButtonWithTitle:@"Save"];
|
[alert addButtonWithTitle:@"Save"];
|
||||||
[alert addButtonWithTitle:@"Cancel"];
|
[alert addButtonWithTitle:@"Cancel"];
|
||||||
[alert setMessageText:@"Change Password"];
|
[alert setMessageText:@"Change Password"];
|
||||||
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedElement.siteName )];
|
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.siteName )];
|
||||||
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
|
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
|
||||||
[alert layout];
|
[alert layout];
|
||||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||||
@@ -396,19 +397,19 @@
|
|||||||
|
|
||||||
- (IBAction)changeType:(id)sender {
|
- (IBAction)changeType:(id)sender {
|
||||||
|
|
||||||
MPElementModel *element = self.selectedElement;
|
MPSiteModel *site = self.selectedSite;
|
||||||
NSArray *types = [element.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
|
NSArray *types = [site.algorithm allTypesStartingWith:MPSiteTypeGeneratedPIN];
|
||||||
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
|
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
|
||||||
for (NSUInteger t = 0; t < [types count]; ++t) {
|
for (NSUInteger t = 0; t < [types count]; ++t) {
|
||||||
MPElementType type = [types[t] unsignedIntegerValue];
|
MPSiteType type = [types[t] unsignedIntegerValue];
|
||||||
NSString *title = [element.algorithm nameOfType:type];
|
NSString *title = [site.algorithm nameOfType:type];
|
||||||
if (type & MPElementTypeClassGenerated)
|
if (type & MPSiteTypeClassGenerated)
|
||||||
title = [element.algorithm generatePasswordForSiteNamed:element.siteName ofType:type
|
title = [site.algorithm generatePasswordForSiteNamed:site.siteName ofType:type
|
||||||
withCounter:element.counter usingKey:[MPMacAppDelegate get].key];
|
withCounter:site.counter usingKey:[MPMacAppDelegate get].key];
|
||||||
|
|
||||||
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
|
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
|
||||||
cell.tag = type;
|
cell.tag = type;
|
||||||
cell.state = type == element.type? NSOnState: NSOffState;
|
cell.state = type == site.type? NSOnState: NSOffState;
|
||||||
cell.title = title;
|
cell.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +417,7 @@
|
|||||||
[alert addButtonWithTitle:@"Save"];
|
[alert addButtonWithTitle:@"Save"];
|
||||||
[alert addButtonWithTitle:@"Cancel"];
|
[alert addButtonWithTitle:@"Cancel"];
|
||||||
[alert setMessageText:@"Change Password Type"];
|
[alert setMessageText:@"Change Password Type"];
|
||||||
[alert setInformativeText:strf( @"Choose a new password type for: %@", element.siteName )];
|
[alert setInformativeText:strf( @"Choose a new password type for: %@", site.siteName )];
|
||||||
[alert setAccessoryView:self.passwordTypesBox];
|
[alert setAccessoryView:self.passwordTypesBox];
|
||||||
[alert layout];
|
[alert layout];
|
||||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||||
@@ -428,11 +429,11 @@
|
|||||||
- (BOOL)handleCommand:(SEL)commandSelector {
|
- (BOOL)handleCommand:(SEL)commandSelector {
|
||||||
|
|
||||||
if (commandSelector == @selector( moveUp: )) {
|
if (commandSelector == @selector( moveUp: )) {
|
||||||
[self.elementsController selectPrevious:self];
|
[self.sitesController selectPrevious:self];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
if (commandSelector == @selector( moveDown: )) {
|
if (commandSelector == @selector( moveDown: )) {
|
||||||
[self.elementsController selectNext:self];
|
[self.sitesController selectNext:self];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
if (commandSelector == @selector( insertNewline: )) {
|
if (commandSelector == @selector( insertNewline: )) {
|
||||||
@@ -449,19 +450,19 @@
|
|||||||
|
|
||||||
- (void)useSite {
|
- (void)useSite {
|
||||||
|
|
||||||
MPElementModel *selectedElement = [self selectedElement];
|
MPSiteModel *selectedSite = [self selectedSite];
|
||||||
if (selectedElement) {
|
if (selectedSite) {
|
||||||
// Performing action while content is available. Copy it.
|
// Performing action while content is available. Copy it.
|
||||||
[self copyContent:selectedElement.content];
|
[self copyContent:selectedSite.content];
|
||||||
|
|
||||||
[self fadeOut];
|
[self fadeOut];
|
||||||
|
|
||||||
NSUserNotification *notification = [NSUserNotification new];
|
NSUserNotification *notification = [NSUserNotification new];
|
||||||
notification.title = @"Password Copied";
|
notification.title = @"Password Copied";
|
||||||
if (selectedElement.loginName.length)
|
if (selectedSite.loginName.length)
|
||||||
notification.subtitle = strf( @"%@ at %@", selectedElement.loginName, selectedElement.siteName );
|
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.siteName );
|
||||||
else
|
else
|
||||||
notification.subtitle = selectedElement.siteName;
|
notification.subtitle = selectedSite.siteName;
|
||||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -499,22 +500,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[self updateElements];
|
[self updateSites];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateElements {
|
- (void)updateSites {
|
||||||
|
|
||||||
if (![MPMacAppDelegate get].key) {
|
if (![MPMacAppDelegate get].key) {
|
||||||
self.elements = nil;
|
self.sites = nil;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PearlProfiler *profiler = [PearlProfiler profilerForTask:@"updateElements"];
|
PearlProfiler *profiler = [PearlProfiler profilerForTask:@"updateSites"];
|
||||||
NSString *query = [self query];
|
NSString *query = [self query];
|
||||||
[profiler finishJob:@"query"];
|
[profiler finishJob:@"query"];
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
|
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
|
||||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||||
query, query, [[MPMacAppDelegate get] activeUserInContext:context]];
|
query, query, [[MPMacAppDelegate get] activeUserInContext:context]];
|
||||||
@@ -523,17 +524,17 @@
|
|||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
|
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
|
||||||
if (!siteResults) {
|
if (!siteResults) {
|
||||||
err( @"While fetching elements for completion: %@", error );
|
err( @"While fetching sites for completion: %@", [error fullDescription] );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[profiler finishJob:@"do fetch"];
|
[profiler finishJob:@"do fetch"];
|
||||||
|
|
||||||
NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:[siteResults count]];
|
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
|
||||||
for (MPElementEntity *element in siteResults)
|
for (MPSiteEntity *site in siteResults)
|
||||||
[newElements addObject:[[MPElementModel alloc] initWithEntity:element]];
|
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site]];
|
||||||
[profiler finishJob:@"make models"];
|
[profiler finishJob:@"make models"];
|
||||||
self.elements = newElements;
|
self.sites = newSites;
|
||||||
[profiler finishJob:@"update elements"];
|
[profiler finishJob:@"update sites"];
|
||||||
}];
|
}];
|
||||||
[profiler finishJob:@"done"];
|
[profiler finishJob:@"done"];
|
||||||
}
|
}
|
||||||
@@ -545,7 +546,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *siteName = self.selectedElement.siteName;
|
NSString *siteName = self.selectedSite.siteName;
|
||||||
if (!siteName)
|
if (!siteName)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -558,14 +559,14 @@
|
|||||||
NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length );
|
NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length );
|
||||||
}
|
}
|
||||||
|
|
||||||
[self.siteTable scrollRowToVisible:(NSInteger)self.elementsController.selectionIndex];
|
[self.siteTable scrollRowToVisible:(NSInteger)self.sitesController.selectionIndex];
|
||||||
[self updateGradient];
|
[self updateGradient];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateGradient {
|
- (void)updateGradient {
|
||||||
|
|
||||||
NSView *siteScrollView = self.siteTable.superview.superview;
|
NSView *siteScrollView = self.siteTable.superview.superview;
|
||||||
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.elementsController.selectionIndex)];
|
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.sitesController.selectionIndex)];
|
||||||
CGFloat selectedOffset = [siteScrollView convertPoint:selectedCellFrame.origin fromView:self.siteTable].y;
|
CGFloat selectedOffset = [siteScrollView convertPoint:selectedCellFrame.origin fromView:self.siteTable].y;
|
||||||
CGFloat gradientOpacity = selectedOffset / siteScrollView.bounds.size.height;
|
CGFloat gradientOpacity = selectedOffset / siteScrollView.bounds.size.height;
|
||||||
self.siteGradient.colors = @[
|
self.siteGradient.colors = @[
|
||||||
@@ -596,7 +597,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
|
||||||
[[self.selectedElement entityInContext:moc] use];
|
[[self.selectedSite entityInContext:moc] use];
|
||||||
[moc saveToStore];
|
[moc saveToStore];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
|
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="blurView" destination="Bwc-sd-6gm" id="wNV-0x-LJn"/>
|
<outlet property="blurView" destination="Bwc-sd-6gm" id="wNV-0x-LJn"/>
|
||||||
<outlet property="elementsController" destination="mcS-ik-b0n" id="cdF-BL-lfg"/>
|
<outlet property="sitesController" destination="mcS-ik-b0n" id="cdF-BL-lfg"/>
|
||||||
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
|
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
|
||||||
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
|
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
|
||||||
<outlet property="passwordTypesMatrix" destination="3fr-Fd-pxx" id="T8g-mS-lxP"/>
|
<outlet property="passwordTypesMatrix" destination="3fr-Fd-pxx" id="T8g-mS-lxP"/>
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="512" height="147"/>
|
<rect key="frame" x="0.0" y="0.0" width="512" height="147"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPElementsTableView">
|
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPSitesTableView">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="515" height="147"/>
|
<rect key="frame" x="0.0" y="0.0" width="515" height="147"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<size key="intercellSpacing" width="3" height="2"/>
|
<size key="intercellSpacing" width="3" height="2"/>
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="calibratedRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="calibratedRGB"/>
|
||||||
</searchFieldCell>
|
</searchFieldCell>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="doSearchElements:" target="-2" id="NJO-iR-OXt"/>
|
<action selector="doSearchSites:" target="-2" id="NJO-iR-OXt"/>
|
||||||
<binding destination="-2" name="hidden" keyPath="locked" id="fAX-uK-cgn"/>
|
<binding destination="-2" name="hidden" keyPath="locked" id="fAX-uK-cgn"/>
|
||||||
<outlet property="delegate" destination="-2" id="2WA-uI-asx"/>
|
<outlet property="delegate" destination="-2" id="2WA-uI-asx"/>
|
||||||
</connections>
|
</connections>
|
||||||
@@ -371,7 +371,7 @@
|
|||||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||||
</buttonCell>
|
</buttonCell>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="deleteElement:" target="-2" id="mVT-O6-KfN"/>
|
<action selector="deleteSite:" target="-2" id="mVT-O6-KfN"/>
|
||||||
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="eqU-d2-XhQ">
|
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="eqU-d2-XhQ">
|
||||||
<dictionary key="options">
|
<dictionary key="options">
|
||||||
<string key="NSValueTransformerName">NSNegateBoolean</string>
|
<string key="NSValueTransformerName">NSNegateBoolean</string>
|
||||||
@@ -869,9 +869,9 @@
|
|||||||
</view>
|
</view>
|
||||||
</window>
|
</window>
|
||||||
<userDefaultsController representsSharedInstance="YES" id="yy2-3W-Ocj"/>
|
<userDefaultsController representsSharedInstance="YES" id="yy2-3W-Ocj"/>
|
||||||
<arrayController objectClassName="MPElementModel" id="mcS-ik-b0n">
|
<arrayController objectClassName="MPSiteModel" id="mcS-ik-b0n">
|
||||||
<connections>
|
<connections>
|
||||||
<binding destination="-2" name="contentArray" keyPath="elements" id="c96-Dv-HK1"/>
|
<binding destination="-2" name="contentArray" keyPath="sites" id="c96-Dv-HK1"/>
|
||||||
</connections>
|
</connections>
|
||||||
</arrayController>
|
</arrayController>
|
||||||
<box autoresizesSubviews="NO" title="Password Types" borderType="line" titlePosition="noTitle" id="bZe-7q-i6q">
|
<box autoresizesSubviews="NO" title="Password Types" borderType="line" titlePosition="noTitle" id="bZe-7q-i6q">
|
||||||
|
|||||||
@@ -9,20 +9,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPElementModel.h
|
// MPSiteModel.h
|
||||||
// MPElementModel
|
// MPSiteModel
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2/11/2014.
|
// Created by lhunath on 2/11/2014.
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
@class MPElementEntity;
|
@class MPSiteEntity;
|
||||||
|
|
||||||
@interface MPElementModel : NSObject
|
@interface MPSiteModel : NSObject
|
||||||
|
|
||||||
@property (nonatomic) NSString *siteName;
|
@property (nonatomic) NSString *siteName;
|
||||||
@property (nonatomic) MPElementType type;
|
@property (nonatomic) MPSiteType type;
|
||||||
@property (nonatomic) NSString *typeName;
|
@property (nonatomic) NSString *typeName;
|
||||||
@property (nonatomic) NSString *content;
|
@property (nonatomic) NSString *content;
|
||||||
@property (nonatomic) NSString *contentDisplay;
|
@property (nonatomic) NSString *contentDisplay;
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
@property (nonatomic) BOOL generated;
|
@property (nonatomic) BOOL generated;
|
||||||
@property (nonatomic) BOOL stored;
|
@property (nonatomic) BOOL stored;
|
||||||
|
|
||||||
- (id)initWithEntity:(MPElementEntity *)entity;
|
- (id)initWithEntity:(MPSiteEntity *)entity;
|
||||||
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc;
|
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;
|
||||||
|
|
||||||
- (void)updateContent;
|
- (void)updateContent;
|
||||||
@end
|
@end
|
||||||
@@ -9,26 +9,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPElementModel.h
|
// MPSiteModel.h
|
||||||
// MPElementModel
|
// MPSiteModel
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2/11/2014.
|
// Created by lhunath on 2/11/2014.
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPElementModel.h"
|
#import "MPSiteModel.h"
|
||||||
#import "MPElementEntity.h"
|
#import "MPSiteEntity.h"
|
||||||
#import "MPEntities.h"
|
#import "MPEntities.h"
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "MPMacAppDelegate.h"
|
#import "MPMacAppDelegate.h"
|
||||||
|
|
||||||
@implementation MPElementModel {
|
@implementation MPSiteModel {
|
||||||
NSManagedObjectID *_entityOID;
|
NSManagedObjectID *_entityOID;
|
||||||
BOOL _initialized;
|
BOOL _initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithEntity:(MPElementEntity *)entity {
|
- (id)initWithEntity:(MPSiteEntity *)entity {
|
||||||
|
|
||||||
if (!(self = [super init]))
|
if (!(self = [super init]))
|
||||||
return nil;
|
return nil;
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setEntity:(MPElementEntity *)entity {
|
- (void)setEntity:(MPSiteEntity *)entity {
|
||||||
|
|
||||||
if ([_entityOID isEqual:entity.objectID])
|
if ([_entityOID isEqual:entity.objectID])
|
||||||
return;
|
return;
|
||||||
@@ -52,21 +52,21 @@
|
|||||||
self.type = entity.type;
|
self.type = entity.type;
|
||||||
self.typeName = entity.typeName;
|
self.typeName = entity.typeName;
|
||||||
self.uses = entity.uses_;
|
self.uses = entity.uses_;
|
||||||
self.counter = [entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0;
|
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: 0;
|
||||||
|
|
||||||
// Find all password types and the index of the current type amongst them.
|
// Find all password types and the index of the current type amongst them.
|
||||||
[self updateContent:entity];
|
[self updateContent:entity];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
|
||||||
|
|
||||||
if (!_entityOID)
|
if (!_entityOID)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
NSError *error;
|
NSError *error;
|
||||||
MPElementEntity *entity = (MPElementEntity *)[moc existingObjectWithID:_entityOID error:&error];
|
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:_entityOID error:&error];
|
||||||
if (!entity)
|
if (!entity)
|
||||||
err( @"Couldn't retrieve active element: %@", error );
|
err( @"Couldn't retrieve active site: %@", [error fullDescription] );
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
@@ -82,9 +82,9 @@
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *entity = [self entityInContext:context];
|
MPSiteEntity *entity = [self entityInContext:context];
|
||||||
if ([entity isKindOfClass:[MPElementGeneratedEntity class]]) {
|
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
|
||||||
((MPElementGeneratedEntity *)entity).counter = counter;
|
((MPGeneratedSiteEntity *)entity).counter = counter;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[self updateContent:entity];
|
[self updateContent:entity];
|
||||||
@@ -94,22 +94,22 @@
|
|||||||
|
|
||||||
- (BOOL)generated {
|
- (BOOL)generated {
|
||||||
|
|
||||||
return self.type & MPElementTypeClassGenerated;
|
return self.type & MPSiteTypeClassGenerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)stored {
|
- (BOOL)stored {
|
||||||
|
|
||||||
return self.type & MPElementTypeClassStored;
|
return self.type & MPSiteTypeClassStored;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateContent {
|
- (void)updateContent {
|
||||||
|
|
||||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[self updateContent:[MPElementEntity existingObjectWithID:_entityOID inContext:context]];
|
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateContent:(MPElementEntity *)entity {
|
- (void)updateContent:(MPSiteEntity *)entity {
|
||||||
|
|
||||||
static NSRegularExpression *re_anyChar;
|
static NSRegularExpression *re_anyChar;
|
||||||
static dispatch_once_t once = 0;
|
static dispatch_once_t once = 0;
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPElementsTableView.h
|
// MPSitesTableView.h
|
||||||
// MPElementsTableView
|
// MPSitesTableView
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2014-06-30.
|
// Created by lhunath on 2014-06-30.
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
@class MPPasswordWindowController;
|
@class MPPasswordWindowController;
|
||||||
|
|
||||||
@interface MPElementsTableView : NSTableView
|
@interface MPSitesTableView : NSTableView
|
||||||
|
|
||||||
@property(nonatomic, weak) MPPasswordWindowController *controller;
|
@property(nonatomic, weak) MPPasswordWindowController *controller;
|
||||||
|
|
||||||
@@ -9,17 +9,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPElementsTableView.h
|
// MPSitesTableView.h
|
||||||
// MPElementsTableView
|
// MPSitesTableView
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2014-06-30.
|
// Created by lhunath on 2014-06-30.
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "MPElementsTableView.h"
|
#import "MPSitesTableView.h"
|
||||||
#import "MPPasswordWindowController.h"
|
#import "MPPasswordWindowController.h"
|
||||||
|
|
||||||
@implementation MPElementsTableView
|
@implementation MPSitesTableView
|
||||||
|
|
||||||
- (void)doCommandBySelector:(SEL)aSelector {
|
- (void)doCommandBySelector:(SEL)aSelector {
|
||||||
|
|
||||||
@@ -19,6 +19,10 @@
|
|||||||
<string></string>
|
<string></string>
|
||||||
<key>CFBundleTypeIconFiles</key>
|
<key>CFBundleTypeIconFiles</key>
|
||||||
<array/>
|
<array/>
|
||||||
|
<key>CFBundleTypeMIMETypes</key>
|
||||||
|
<array>
|
||||||
|
<string>text/plain</string>
|
||||||
|
</array>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Master Password sites</string>
|
<string>Master Password sites</string>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
@@ -27,8 +31,10 @@
|
|||||||
<string>Owner</string>
|
<string>Owner</string>
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>com.lyndir.lhunath.MasterPassword.sites</string>
|
<string>com.lyndir.masterpassword.sites</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@@ -47,19 +53,6 @@
|
|||||||
<string>[auto]</string>
|
<string>[auto]</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeRole</key>
|
|
||||||
<string>Editor</string>
|
|
||||||
<key>CFBundleURLName</key>
|
|
||||||
<string>com.lyndir.lhunath.MasterPassword</string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>com.lyndir.lhunath.MasterPassword</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>[auto]</string>
|
<string>[auto]</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
@@ -77,12 +70,16 @@
|
|||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.utf8-plain-text</string>
|
||||||
|
</array>
|
||||||
<key>UTTypeDescription</key>
|
<key>UTTypeDescription</key>
|
||||||
<string>Master Password sites</string>
|
<string>Master Password sites</string>
|
||||||
<key>UTTypeIconFile</key>
|
<key>UTTypeIconFile</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>UTTypeIdentifier</key>
|
<key>UTTypeIdentifier</key>
|
||||||
<string>com.lyndir.lhunath.MasterPassword.sites</string>
|
<string>com.lyndir.masterpassword.sites</string>
|
||||||
<key>UTTypeSize320IconFile</key>
|
<key>UTTypeSize320IconFile</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>UTTypeSize64IconFile</key>
|
<key>UTTypeSize64IconFile</key>
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
|
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
|
||||||
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
|
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
|
||||||
93D394C4254EEB45FB335AFB /* MPElementsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39423D7BF4FD31FE6D27C /* MPElementsTableView.m */; };
|
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */; };
|
||||||
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
|
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
|
||||||
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */; };
|
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */; };
|
||||||
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */; };
|
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */; };
|
||||||
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
||||||
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */; };
|
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */; };
|
||||||
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */; };
|
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */; };
|
||||||
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
|
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
|
||||||
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */; };
|
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */; };
|
||||||
@@ -43,6 +43,11 @@
|
|||||||
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */; };
|
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */; };
|
||||||
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
|
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
|
||||||
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
|
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
|
||||||
|
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */; };
|
||||||
|
DA32CFDC19CF1C70004F3F0E /* MPStoredSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */; };
|
||||||
|
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */; };
|
||||||
|
DA32CFE219CF1C71004F3F0E /* MPSiteQuestionEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */; };
|
||||||
|
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */; };
|
||||||
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; };
|
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; };
|
||||||
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; };
|
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; };
|
||||||
DA3B844F190FC60900246EEA /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3B844A190FC5A900246EEA /* Crashlytics.framework */; };
|
DA3B844F190FC60900246EEA /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3B844A190FC5A900246EEA /* Crashlytics.framework */; };
|
||||||
@@ -60,12 +65,8 @@
|
|||||||
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA01724A667003798D8 /* MPAppDelegate_Shared.m */; };
|
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA01724A667003798D8 /* MPAppDelegate_Shared.m */; };
|
||||||
DA5E5CFB1724A667003798D8 /* MPAppDelegate_Store.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */; };
|
DA5E5CFB1724A667003798D8 /* MPAppDelegate_Store.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */; };
|
||||||
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA41724A667003798D8 /* MPConfig.m */; };
|
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA41724A667003798D8 /* MPConfig.m */; };
|
||||||
DA5E5CFD1724A667003798D8 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA61724A667003798D8 /* MPElementEntity.m */; };
|
|
||||||
DA5E5CFE1724A667003798D8 /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA81724A667003798D8 /* MPElementGeneratedEntity.m */; };
|
|
||||||
DA5E5CFF1724A667003798D8 /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAA1724A667003798D8 /* MPElementStoredEntity.m */; };
|
|
||||||
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAC1724A667003798D8 /* MPEntities.m */; };
|
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAC1724A667003798D8 /* MPEntities.m */; };
|
||||||
DA5E5D011724A667003798D8 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAE1724A667003798D8 /* MPKey.m */; };
|
DA5E5D011724A667003798D8 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAE1724A667003798D8 /* MPKey.m */; };
|
||||||
DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB11724A667003798D8 /* MPUserEntity.m */; };
|
|
||||||
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */; };
|
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */; };
|
||||||
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB61724A667003798D8 /* MPMacConfig.m */; };
|
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB61724A667003798D8 /* MPMacConfig.m */; };
|
||||||
DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */; };
|
DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */; };
|
||||||
@@ -88,8 +89,6 @@
|
|||||||
DACA22BC1705DE7D002C6C22 /* NSError+UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */; };
|
DACA22BC1705DE7D002C6C22 /* NSError+UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */; };
|
||||||
DACA22BD1705DE7D002C6C22 /* NSError+UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA22B91705DE7D002C6C22 /* NSError+UbiquityStoreManager.m */; };
|
DACA22BD1705DE7D002C6C22 /* NSError+UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA22B91705DE7D002C6C22 /* NSError+UbiquityStoreManager.m */; };
|
||||||
DACA22BE1705DE7D002C6C22 /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22BA1705DE7D002C6C22 /* UbiquityStoreManager.h */; };
|
DACA22BE1705DE7D002C6C22 /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22BA1705DE7D002C6C22 /* UbiquityStoreManager.h */; };
|
||||||
DACA26F91705DF81002C6C22 /* background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24171705DF7D002C6C22 /* background@2x.png */; };
|
|
||||||
DACA26FA1705DF81002C6C22 /* background.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24181705DF7D002C6C22 /* background.png */; };
|
|
||||||
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA241C1705DF7D002C6C22 /* logo-bare.png */; };
|
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA241C1705DF7D002C6C22 /* logo-bare.png */; };
|
||||||
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24321705DF7D002C6C22 /* avatar-13@2x.png */; };
|
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24321705DF7D002C6C22 /* avatar-13@2x.png */; };
|
||||||
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24331705DF7D002C6C22 /* avatar-3@2x.png */; };
|
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24331705DF7D002C6C22 /* avatar-3@2x.png */; };
|
||||||
@@ -229,21 +228,21 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
|
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
|
||||||
93D39240B5143E01F0B75E96 /* MPElementModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementModel.h; sourceTree = "<group>"; };
|
93D39240B5143E01F0B75E96 /* MPSiteModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteModel.h; sourceTree = "<group>"; };
|
||||||
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = "<group>"; };
|
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = "<group>"; };
|
||||||
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInitialWindowController.h; sourceTree = "<group>"; };
|
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInitialWindowController.h; sourceTree = "<group>"; };
|
||||||
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
|
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
|
||||||
93D39423D7BF4FD31FE6D27C /* MPElementsTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementsTableView.m; sourceTree = "<group>"; };
|
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesTableView.m; sourceTree = "<group>"; };
|
||||||
93D394EEFF5BF555A55AF361 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PearlProfiler.h; path = ../../../External/Pearl/Pearl/PearlProfiler.h; sourceTree = "<group>"; };
|
93D394EEFF5BF555A55AF361 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PearlProfiler.h; path = ../../../External/Pearl/Pearl/PearlProfiler.h; sourceTree = "<group>"; };
|
||||||
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
|
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
|
||||||
93D3977484534E99F9BA579D /* MPPasswordWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindow.h; sourceTree = "<group>"; };
|
93D3977484534E99F9BA579D /* MPPasswordWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindow.h; sourceTree = "<group>"; };
|
||||||
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindowController.m; sourceTree = "<group>"; };
|
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindowController.m; sourceTree = "<group>"; };
|
||||||
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
|
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
|
||||||
93D39AC6360DDC16AEAA4119 /* MPElementsTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementsTableView.h; sourceTree = "<group>"; };
|
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesTableView.h; sourceTree = "<group>"; };
|
||||||
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInitialWindowController.m; sourceTree = "<group>"; };
|
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInitialWindowController.m; sourceTree = "<group>"; };
|
||||||
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindow.m; sourceTree = "<group>"; };
|
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindow.m; sourceTree = "<group>"; };
|
||||||
93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PearlProfiler.m; path = ../../../External/Pearl/Pearl/PearlProfiler.m; sourceTree = "<group>"; };
|
93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PearlProfiler.m; path = ../../../External/Pearl/Pearl/PearlProfiler.m; sourceTree = "<group>"; };
|
||||||
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementModel.m; sourceTree = "<group>"; };
|
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteModel.m; sourceTree = "<group>"; };
|
||||||
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
|
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
|
||||||
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
|
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
|
||||||
DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; };
|
DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; };
|
||||||
@@ -274,6 +273,17 @@
|
|||||||
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Crypto.m"; sourceTree = "<group>"; };
|
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Crypto.m"; sourceTree = "<group>"; };
|
||||||
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
|
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
|
||||||
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
|
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
|
||||||
|
DA32CFD719CF1C70004F3F0E /* MPGeneratedSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGeneratedSiteEntity.h; sourceTree = "<group>"; };
|
||||||
|
DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGeneratedSiteEntity.m; sourceTree = "<group>"; };
|
||||||
|
DA32CFDA19CF1C70004F3F0E /* MPStoredSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoredSiteEntity.h; sourceTree = "<group>"; };
|
||||||
|
DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoredSiteEntity.m; sourceTree = "<group>"; };
|
||||||
|
DA32CFDD19CF1C70004F3F0E /* MPSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteEntity.h; sourceTree = "<group>"; };
|
||||||
|
DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteEntity.m; sourceTree = "<group>"; };
|
||||||
|
DA32CFE019CF1C71004F3F0E /* MPSiteQuestionEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteQuestionEntity.h; sourceTree = "<group>"; };
|
||||||
|
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteQuestionEntity.m; sourceTree = "<group>"; };
|
||||||
|
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
||||||
|
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
||||||
|
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = "<group>"; };
|
||||||
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; 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>"; };
|
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
|
||||||
DA3B844A190FC5A900246EEA /* 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>"; };
|
||||||
@@ -302,19 +312,11 @@
|
|||||||
DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Store.m; sourceTree = "<group>"; };
|
DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Store.m; sourceTree = "<group>"; };
|
||||||
DA5E5CA31724A667003798D8 /* MPConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPConfig.h; sourceTree = "<group>"; };
|
DA5E5CA31724A667003798D8 /* MPConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPConfig.h; sourceTree = "<group>"; };
|
||||||
DA5E5CA41724A667003798D8 /* MPConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPConfig.m; sourceTree = "<group>"; };
|
DA5E5CA41724A667003798D8 /* MPConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPConfig.m; sourceTree = "<group>"; };
|
||||||
DA5E5CA51724A667003798D8 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
|
||||||
DA5E5CA61724A667003798D8 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
|
|
||||||
DA5E5CA71724A667003798D8 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
|
||||||
DA5E5CA81724A667003798D8 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
|
||||||
DA5E5CA91724A667003798D8 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
|
||||||
DA5E5CAA1724A667003798D8 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
|
||||||
DA5E5CAB1724A667003798D8 /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
|
DA5E5CAB1724A667003798D8 /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
|
||||||
DA5E5CAC1724A667003798D8 /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
|
DA5E5CAC1724A667003798D8 /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
|
||||||
DA5E5CAD1724A667003798D8 /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
|
DA5E5CAD1724A667003798D8 /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
|
||||||
DA5E5CAE1724A667003798D8 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
|
DA5E5CAE1724A667003798D8 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
|
||||||
DA5E5CAF1724A667003798D8 /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
|
DA5E5CAF1724A667003798D8 /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
|
||||||
DA5E5CB01724A667003798D8 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
|
|
||||||
DA5E5CB11724A667003798D8 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
|
|
||||||
DA5E5CB31724A667003798D8 /* MPMacAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacAppDelegate.h; sourceTree = "<group>"; };
|
DA5E5CB31724A667003798D8 /* MPMacAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacAppDelegate.h; sourceTree = "<group>"; };
|
||||||
DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacAppDelegate.m; sourceTree = "<group>"; };
|
DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacAppDelegate.m; sourceTree = "<group>"; };
|
||||||
DA5E5CB51724A667003798D8 /* MPMacConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacConfig.h; sourceTree = "<group>"; };
|
DA5E5CB51724A667003798D8 /* MPMacConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacConfig.h; sourceTree = "<group>"; };
|
||||||
@@ -747,8 +749,6 @@
|
|||||||
DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+UbiquityStoreManager.h"; 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>"; };
|
DACA22B91705DE7D002C6C22 /* NSError+UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+UbiquityStoreManager.m"; sourceTree = "<group>"; };
|
||||||
DACA22BA1705DE7D002C6C22 /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
|
DACA22BA1705DE7D002C6C22 /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
|
||||||
DACA24171705DF7D002C6C22 /* background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "background@2x.png"; sourceTree = "<group>"; };
|
|
||||||
DACA24181705DF7D002C6C22 /* background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = background.png; sourceTree = "<group>"; };
|
|
||||||
DACA241C1705DF7D002C6C22 /* logo-bare.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "logo-bare.png"; sourceTree = "<group>"; };
|
DACA241C1705DF7D002C6C22 /* logo-bare.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "logo-bare.png"; sourceTree = "<group>"; };
|
||||||
DACA24321705DF7D002C6C22 /* avatar-13@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13@2x.png"; sourceTree = "<group>"; };
|
DACA24321705DF7D002C6C22 /* avatar-13@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13@2x.png"; sourceTree = "<group>"; };
|
||||||
DACA24331705DF7D002C6C22 /* avatar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3@2x.png"; sourceTree = "<group>"; };
|
DACA24331705DF7D002C6C22 /* avatar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3@2x.png"; sourceTree = "<group>"; };
|
||||||
@@ -995,6 +995,16 @@
|
|||||||
DA5E5C961724A667003798D8 /* ObjC */ = {
|
DA5E5C961724A667003798D8 /* ObjC */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */,
|
||||||
|
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */,
|
||||||
|
DA32CFE019CF1C71004F3F0E /* MPSiteQuestionEntity.h */,
|
||||||
|
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */,
|
||||||
|
DA32CFDD19CF1C70004F3F0E /* MPSiteEntity.h */,
|
||||||
|
DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */,
|
||||||
|
DA32CFDA19CF1C70004F3F0E /* MPStoredSiteEntity.h */,
|
||||||
|
DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */,
|
||||||
|
DA32CFD719CF1C70004F3F0E /* MPGeneratedSiteEntity.h */,
|
||||||
|
DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */,
|
||||||
DA3B8454190FC89700246EEA /* MPFixable.m */,
|
DA3B8454190FC89700246EEA /* MPFixable.m */,
|
||||||
DA3B8455190FC89700246EEA /* MPFixable.h */,
|
DA3B8455190FC89700246EEA /* MPFixable.h */,
|
||||||
DA5E5CB21724A667003798D8 /* Mac */,
|
DA5E5CB21724A667003798D8 /* Mac */,
|
||||||
@@ -1012,19 +1022,11 @@
|
|||||||
DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */,
|
DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */,
|
||||||
DA5E5CA31724A667003798D8 /* MPConfig.h */,
|
DA5E5CA31724A667003798D8 /* MPConfig.h */,
|
||||||
DA5E5CA41724A667003798D8 /* MPConfig.m */,
|
DA5E5CA41724A667003798D8 /* MPConfig.m */,
|
||||||
DA5E5CA51724A667003798D8 /* MPElementEntity.h */,
|
|
||||||
DA5E5CA61724A667003798D8 /* MPElementEntity.m */,
|
|
||||||
DA5E5CA71724A667003798D8 /* MPElementGeneratedEntity.h */,
|
|
||||||
DA5E5CA81724A667003798D8 /* MPElementGeneratedEntity.m */,
|
|
||||||
DA5E5CA91724A667003798D8 /* MPElementStoredEntity.h */,
|
|
||||||
DA5E5CAA1724A667003798D8 /* MPElementStoredEntity.m */,
|
|
||||||
DA5E5CAB1724A667003798D8 /* MPEntities.h */,
|
DA5E5CAB1724A667003798D8 /* MPEntities.h */,
|
||||||
DA5E5CAC1724A667003798D8 /* MPEntities.m */,
|
DA5E5CAC1724A667003798D8 /* MPEntities.m */,
|
||||||
DA5E5CAD1724A667003798D8 /* MPKey.h */,
|
DA5E5CAD1724A667003798D8 /* MPKey.h */,
|
||||||
DA5E5CAE1724A667003798D8 /* MPKey.m */,
|
DA5E5CAE1724A667003798D8 /* MPKey.m */,
|
||||||
DA5E5CAF1724A667003798D8 /* MPTypes.h */,
|
DA5E5CAF1724A667003798D8 /* MPTypes.h */,
|
||||||
DA5E5CB01724A667003798D8 /* MPUserEntity.h */,
|
|
||||||
DA5E5CB11724A667003798D8 /* MPUserEntity.m */,
|
|
||||||
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */,
|
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */,
|
||||||
);
|
);
|
||||||
name = ObjC;
|
name = ObjC;
|
||||||
@@ -1046,8 +1048,8 @@
|
|||||||
DA5E5CC41724A667003798D8 /* MainMenu.xib */,
|
DA5E5CC41724A667003798D8 /* MainMenu.xib */,
|
||||||
DA5E5CC61724A667003798D8 /* main.m */,
|
DA5E5CC61724A667003798D8 /* main.m */,
|
||||||
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */,
|
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */,
|
||||||
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */,
|
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */,
|
||||||
93D39240B5143E01F0B75E96 /* MPElementModel.h */,
|
93D39240B5143E01F0B75E96 /* MPSiteModel.h */,
|
||||||
DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */,
|
DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */,
|
||||||
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */,
|
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */,
|
||||||
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */,
|
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */,
|
||||||
@@ -1055,8 +1057,8 @@
|
|||||||
93D3977484534E99F9BA579D /* MPPasswordWindow.h */,
|
93D3977484534E99F9BA579D /* MPPasswordWindow.h */,
|
||||||
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */,
|
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */,
|
||||||
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */,
|
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */,
|
||||||
93D39423D7BF4FD31FE6D27C /* MPElementsTableView.m */,
|
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */,
|
||||||
93D39AC6360DDC16AEAA4119 /* MPElementsTableView.h */,
|
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */,
|
||||||
);
|
);
|
||||||
path = Mac;
|
path = Mac;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1544,7 +1546,6 @@
|
|||||||
DA2509271951B86C00AC23F1 /* screen.png */,
|
DA2509271951B86C00AC23F1 /* screen.png */,
|
||||||
DA0933CF1747B91B00DE1CEF /* appstore.png */,
|
DA0933CF1747B91B00DE1CEF /* appstore.png */,
|
||||||
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */,
|
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */,
|
||||||
DACA24161705DF7D002C6C22 /* Background */,
|
|
||||||
DACA24311705DF7D002C6C22 /* Avatars */,
|
DACA24311705DF7D002C6C22 /* Avatars */,
|
||||||
DACA241C1705DF7D002C6C22 /* logo-bare.png */,
|
DACA241C1705DF7D002C6C22 /* logo-bare.png */,
|
||||||
DACA24581705DF7D002C6C22 /* menu-icon.png */,
|
DACA24581705DF7D002C6C22 /* menu-icon.png */,
|
||||||
@@ -1553,15 +1554,6 @@
|
|||||||
path = Media;
|
path = Media;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
DACA24161705DF7D002C6C22 /* Background */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
DACA24171705DF7D002C6C22 /* background@2x.png */,
|
|
||||||
DACA24181705DF7D002C6C22 /* background.png */,
|
|
||||||
);
|
|
||||||
path = Background;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
DACA24311705DF7D002C6C22 /* Avatars */ = {
|
DACA24311705DF7D002C6C22 /* Avatars */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1943,7 +1935,7 @@
|
|||||||
attributes = {
|
attributes = {
|
||||||
CLASSPREFIX = MP;
|
CLASSPREFIX = MP;
|
||||||
LastTestingUpgradeCheck = 0510;
|
LastTestingUpgradeCheck = 0510;
|
||||||
LastUpgradeCheck = 0510;
|
LastUpgradeCheck = 0600;
|
||||||
ORGANIZATIONNAME = Lyndir;
|
ORGANIZATIONNAME = Lyndir;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
DA5BFA43147E415C00F98B1E = {
|
DA5BFA43147E415C00F98B1E = {
|
||||||
@@ -1958,12 +1950,10 @@
|
|||||||
};
|
};
|
||||||
buildConfigurationList = DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-Mac" */;
|
buildConfigurationList = DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-Mac" */;
|
||||||
compatibilityVersion = "Xcode 3.2";
|
compatibilityVersion = "Xcode 3.2";
|
||||||
developmentRegion = English;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
en,
|
en,
|
||||||
English,
|
|
||||||
es,
|
|
||||||
);
|
);
|
||||||
mainGroup = DA5BFA39147E415C00F98B1E;
|
mainGroup = DA5BFA39147E415C00F98B1E;
|
||||||
productRefGroup = DA5BFA45147E415C00F98B1E /* Products */;
|
productRefGroup = DA5BFA45147E415C00F98B1E /* Products */;
|
||||||
@@ -2002,8 +1992,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
|
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
|
||||||
DACA26F91705DF81002C6C22 /* background@2x.png in Resources */,
|
|
||||||
DACA26FA1705DF81002C6C22 /* background.png in Resources */,
|
|
||||||
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */,
|
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */,
|
||||||
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */,
|
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */,
|
||||||
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */,
|
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */,
|
||||||
@@ -2138,28 +2126,29 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */,
|
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */,
|
||||||
|
DA32CFE219CF1C71004F3F0E /* MPSiteQuestionEntity.m in Sources */,
|
||||||
|
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */,
|
||||||
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
|
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
|
||||||
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
|
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
|
||||||
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
|
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
|
||||||
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
|
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
|
||||||
DA5E5CFB1724A667003798D8 /* MPAppDelegate_Store.m in Sources */,
|
DA5E5CFB1724A667003798D8 /* MPAppDelegate_Store.m in Sources */,
|
||||||
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */,
|
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */,
|
||||||
DA5E5CFD1724A667003798D8 /* MPElementEntity.m in Sources */,
|
|
||||||
DA5E5CFE1724A667003798D8 /* MPElementGeneratedEntity.m in Sources */,
|
|
||||||
DA5E5CFF1724A667003798D8 /* MPElementStoredEntity.m in Sources */,
|
|
||||||
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */,
|
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */,
|
||||||
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
|
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
|
||||||
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
|
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
|
||||||
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
|
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
|
||||||
DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */,
|
DA32CFDC19CF1C70004F3F0E /* MPStoredSiteEntity.m in Sources */,
|
||||||
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */,
|
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */,
|
||||||
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */,
|
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */,
|
||||||
DA5E5D0C1724A667003798D8 /* main.m in Sources */,
|
DA5E5D0C1724A667003798D8 /* main.m in Sources */,
|
||||||
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */,
|
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */,
|
||||||
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */,
|
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */,
|
||||||
|
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */,
|
||||||
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */,
|
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */,
|
||||||
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
|
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
|
||||||
93D394C4254EEB45FB335AFB /* MPElementsTableView.m in Sources */,
|
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
|
||||||
|
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -2265,6 +2254,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "Debug-Mac";
|
name = "Debug-Mac";
|
||||||
};
|
};
|
||||||
@@ -2272,6 +2262,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "AdHoc-Mac";
|
name = "AdHoc-Mac";
|
||||||
};
|
};
|
||||||
@@ -2279,6 +2270,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "AppStore-Mac";
|
name = "AppStore-Mac";
|
||||||
};
|
};
|
||||||
@@ -2286,6 +2278,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "Debug-Mac";
|
name = "Debug-Mac";
|
||||||
};
|
};
|
||||||
@@ -2293,6 +2286,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "AdHoc-Mac";
|
name = "AdHoc-Mac";
|
||||||
};
|
};
|
||||||
@@ -2300,6 +2294,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "AppStore-Mac";
|
name = "AppStore-Mac";
|
||||||
};
|
};
|
||||||
@@ -2320,11 +2315,13 @@
|
|||||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||||
@@ -2393,11 +2390,13 @@
|
|||||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
@@ -2515,11 +2514,13 @@
|
|||||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
@@ -2600,6 +2601,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DSTROOT = /tmp/Pearl.dst;
|
DSTROOT = /tmp/Pearl.dst;
|
||||||
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
@@ -2619,6 +2621,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = NO;
|
CLANG_ENABLE_OBJC_ARC = NO;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DSTROOT = /tmp/jrswizzle.dst;
|
DSTROOT = /tmp/jrswizzle.dst;
|
||||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
@@ -2630,18 +2633,21 @@
|
|||||||
DABC6C0B175D8C85000C15D4 /* Debug-Mac */ = {
|
DABC6C0B175D8C85000C15D4 /* Debug-Mac */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "Debug-Mac";
|
name = "Debug-Mac";
|
||||||
};
|
};
|
||||||
DABC6C0C175D8C85000C15D4 /* AdHoc-Mac */ = {
|
DABC6C0C175D8C85000C15D4 /* AdHoc-Mac */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "AdHoc-Mac";
|
name = "AdHoc-Mac";
|
||||||
};
|
};
|
||||||
DABC6C0D175D8C85000C15D4 /* AppStore-Mac */ = {
|
DABC6C0D175D8C85000C15D4 /* AppStore-Mac */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
};
|
};
|
||||||
name = "AppStore-Mac";
|
name = "AppStore-Mac";
|
||||||
};
|
};
|
||||||
@@ -2649,6 +2655,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = NO;
|
CLANG_ENABLE_OBJC_ARC = NO;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DSTROOT = /tmp/jrswizzle.dst;
|
DSTROOT = /tmp/jrswizzle.dst;
|
||||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
@@ -2661,6 +2668,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = NO;
|
CLANG_ENABLE_OBJC_ARC = NO;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DSTROOT = /tmp/jrswizzle.dst;
|
DSTROOT = /tmp/jrswizzle.dst;
|
||||||
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
@@ -2673,6 +2681,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DSTROOT = /tmp/Pearl.dst;
|
DSTROOT = /tmp/Pearl.dst;
|
||||||
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
@@ -2692,6 +2701,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DSTROOT = /tmp/Pearl.dst;
|
DSTROOT = /tmp/Pearl.dst;
|
||||||
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
@@ -2791,8 +2801,9 @@
|
|||||||
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */,
|
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */,
|
||||||
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */,
|
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */,
|
||||||
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */,
|
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */,
|
||||||
|
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */,
|
||||||
);
|
);
|
||||||
currentVersion = DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */;
|
currentVersion = DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */;
|
||||||
path = MasterPassword.xcdatamodeld;
|
path = MasterPassword.xcdatamodeld;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
versionGroupType = wrapper.xcdatamodel;
|
versionGroupType = wrapper.xcdatamodel;
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>_XCCurrentVersionName</key>
|
<key>_XCCurrentVersionName</key>
|
||||||
<string>MasterPassword 5.xcdatamodel</string>
|
<string>MasterPassword 6.xcdatamodel</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
|
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
|
||||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements"
|
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||||
inverseEntity="MPUserEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
<entity name="MPUserEntity" representedClassName="MPUserEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES">
|
||||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
|
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity"
|
<userInfo/>
|
||||||
inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
</attribute>
|
||||||
|
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
|
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
|
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
lastSavedToolsVersion="2057" systemVersion="12C60" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
|
||||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||||
@@ -12,16 +11,15 @@
|
|||||||
<attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
|
<attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements"
|
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||||
inverseEntity="MPUserEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
<entity name="MPUserEntity" representedClassName="MPUserEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES">
|
||||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||||
@@ -31,8 +29,7 @@
|
|||||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||||
<userInfo/>
|
<userInfo/>
|
||||||
</attribute>
|
</attribute>
|
||||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity"
|
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||||
inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="MPElementEntity" positionX="0" positionY="0" width="128" height="180"/>
|
<element name="MPElementEntity" positionX="0" positionY="0" width="128" height="180"/>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
|
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
lastSavedToolsVersion="1810" systemVersion="12B19" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
|
||||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
||||||
@@ -12,13 +11,12 @@
|
|||||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements"
|
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||||
inverseEntity="MPUserEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||||
@@ -31,8 +29,7 @@
|
|||||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||||
<userInfo/>
|
<userInfo/>
|
||||||
</attribute>
|
</attribute>
|
||||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity"
|
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
|
||||||
inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
|
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
|
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
lastSavedToolsVersion="2057" systemVersion="12C60" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
|
||||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
||||||
@@ -12,13 +11,12 @@
|
|||||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements"
|
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||||
inverseEntity="MPUserEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||||
@@ -30,8 +28,7 @@
|
|||||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||||
<userInfo/>
|
<userInfo/>
|
||||||
</attribute>
|
</attribute>
|
||||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity"
|
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
|
||||||
inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
|
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||||
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||||
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||||
<userInfo/>
|
<userInfo/>
|
||||||
</attribute>
|
</attribute>
|
||||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="193"/>
|
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="193"/>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
|
<entity name="MPGeneratedSiteEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPSiteEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
|
||||||
|
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="MPSiteEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
|
||||||
|
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
|
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||||
|
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||||
|
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||||
|
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
|
||||||
|
<userInfo/>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
|
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
|
||||||
|
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
|
||||||
|
<relationship name="questions" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MPSiteQuestionEntity" inverseName="site" inverseEntity="MPSiteQuestionEntity" syncable="YES"/>
|
||||||
|
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="sites" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="MPSiteQuestionEntity" representedClassName="MPSiteQuestionEntity" syncable="YES">
|
||||||
|
<attribute name="keyword" attributeType="String" syncable="YES"/>
|
||||||
|
<relationship name="site" maxCount="1" deletionRule="Nullify" destinationEntity="MPSiteEntity" inverseName="questions" inverseEntity="MPSiteEntity" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="MPStoredSiteEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPSiteEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
|
||||||
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||||
|
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||||
|
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||||
|
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||||
|
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
|
||||||
|
<userInfo/>
|
||||||
|
</attribute>
|
||||||
|
<relationship name="sites" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPSiteEntity" inverseName="user" inverseEntity="MPSiteEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
|
<elements>
|
||||||
|
<element name="MPGeneratedSiteEntity" positionX="216" positionY="-288" width="128" height="58"/>
|
||||||
|
<element name="MPSiteEntity" positionX="-0" positionY="-286" width="128" height="208"/>
|
||||||
|
<element name="MPSiteQuestionEntity" positionX="-2" positionY="-9" width="128" height="73"/>
|
||||||
|
<element name="MPStoredSiteEntity" positionX="214" positionY="-171" width="128" height="58"/>
|
||||||
|
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="148"/>
|
||||||
|
</elements>
|
||||||
|
</model>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// AttributedMarkdown.h
|
||||||
|
// AttributedMarkdown
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-28.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@interface AttributedMarkdown : NSObject
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// AttributedMarkdown.m
|
||||||
|
// AttributedMarkdown
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 2014-09-28.
|
||||||
|
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AttributedMarkdown.h"
|
||||||
|
|
||||||
|
@implementation AttributedMarkdown
|
||||||
|
|
||||||
|
@end
|
||||||
24
MasterPassword/ObjC/iOS/AttributedMarkdownTests/Info.plist
Normal file
24
MasterPassword/ObjC/iOS/AttributedMarkdownTests/Info.plist
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.lyndir.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>BNDL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
46
MasterPassword/ObjC/iOS/MPAnswersViewController.h
Normal file
46
MasterPassword/ObjC/iOS/MPAnswersViewController.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// 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"
|
||||||
|
#import "MPSiteQuestionEntity.h"
|
||||||
|
|
||||||
|
@interface MPAnswersViewController : UIViewController
|
||||||
|
|
||||||
|
@property (nonatomic) IBOutlet UITableView *tableView;
|
||||||
|
|
||||||
|
- (void)setSite:(MPSiteEntity *)site;
|
||||||
|
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPGlobalAnswersCell : UITableViewCell
|
||||||
|
|
||||||
|
@property (nonatomic) IBOutlet UILabel *titleLabel;
|
||||||
|
@property (nonatomic) IBOutlet UITextField *answerField;
|
||||||
|
|
||||||
|
- (void)setSite:(MPSiteEntity *)site;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPSendAnswersCell : UITableViewCell
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPMultipleAnswersCell : UITableViewCell <UITextFieldDelegate>
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPAnswersQuestionCell : UITableViewCell
|
||||||
|
|
||||||
|
@property(nonatomic) IBOutlet UITextField *questionField;
|
||||||
|
@property(nonatomic) IBOutlet UITextField *answerField;
|
||||||
|
|
||||||
|
- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site inVC:(MPAnswersViewController *)VC;
|
||||||
|
|
||||||
|
@end
|
||||||
339
MasterPassword/ObjC/iOS/MPAnswersViewController.m
Normal file
339
MasterPassword/ObjC/iOS/MPAnswersViewController.m
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
//
|
||||||
|
// MPPreferencesViewController.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 04/06/12.
|
||||||
|
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPAnswersViewController.h"
|
||||||
|
#import "MPiOSAppDelegate.h"
|
||||||
|
#import "MPAppDelegate_Store.h"
|
||||||
|
#import "MPOverlayViewController.h"
|
||||||
|
|
||||||
|
@interface MPAnswersViewController()
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPAnswersViewController {
|
||||||
|
NSManagedObjectID *_siteOID;
|
||||||
|
BOOL _multiple;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Life
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.tableView.tableHeaderView = [UIView new];
|
||||||
|
self.tableView.tableFooterView = [UIView new];
|
||||||
|
self.view.backgroundColor = [UIColor clearColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillAppear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPAnswersViewController *self, NSNotification *note) {
|
||||||
|
if (![note.userInfo[@"animated"] boolValue])
|
||||||
|
[UIView setAnimationsEnabled:NO];
|
||||||
|
[[MPOverlaySegue dismissViewController:self] perform];
|
||||||
|
[UIView setAnimationsEnabled:YES];
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
|
PearlRemoveNotificationObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||||
|
|
||||||
|
return UIStatusBarStyleLightContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - State
|
||||||
|
|
||||||
|
- (void)setSite:(MPSiteEntity *)site {
|
||||||
|
|
||||||
|
_siteOID = [site objectID];
|
||||||
|
_multiple = [site.questions count] > 0;
|
||||||
|
[self.tableView reloadData];
|
||||||
|
[self updateAnimated:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setMultiple:(BOOL)multiple animated:(BOOL)animated {
|
||||||
|
|
||||||
|
_multiple = multiple;
|
||||||
|
[self updateAnimated:animated];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
|
return [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDelegate
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||||
|
|
||||||
|
if (section == 0)
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
if (!_multiple)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return [[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].questions count] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
|
if (indexPath.section == 0) {
|
||||||
|
if (indexPath.item == 0) {
|
||||||
|
MPGlobalAnswersCell *cell = [MPGlobalAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||||
|
[cell setSite:site];
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
if (indexPath.item == 1)
|
||||||
|
return [MPSendAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||||
|
if (indexPath.item == 2) {
|
||||||
|
MPMultipleAnswersCell *cell = [MPMultipleAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||||
|
cell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
Throw( @"Unsupported row index: %@", indexPath );
|
||||||
|
}
|
||||||
|
|
||||||
|
MPAnswersQuestionCell *cell = [MPAnswersQuestionCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||||
|
MPSiteQuestionEntity *question = nil;
|
||||||
|
if ([site.questions count] > indexPath.item)
|
||||||
|
question = site.questions[indexPath.item];
|
||||||
|
[cell setQuestion:question forSite:site inVC:self];
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDelegate
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
if (indexPath.section == 0) {
|
||||||
|
if (indexPath.item == 0)
|
||||||
|
return 133;
|
||||||
|
return 44;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
|
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
|
||||||
|
if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) {
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
|
||||||
|
[UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text;
|
||||||
|
}
|
||||||
|
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
|
||||||
|
if (!_multiple)
|
||||||
|
[self setMultiple:YES animated:YES];
|
||||||
|
|
||||||
|
else if (_multiple) {
|
||||||
|
if (![site.questions count])
|
||||||
|
[self setMultiple:NO animated:YES];
|
||||||
|
|
||||||
|
else
|
||||||
|
[PearlAlert showAlertWithTitle:@"Remove Site Questions?" message:
|
||||||
|
@"Do you want to remove the questions you have configured for this site?"
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||||
|
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == [alert cancelButtonIndex])
|
||||||
|
return;
|
||||||
|
|
||||||
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
MPSiteEntity *site_ = [self siteInContext:context];
|
||||||
|
NSOrderedSet *questions = [site_.questions copy];
|
||||||
|
for (MPSiteQuestionEntity *question in questions)
|
||||||
|
[context deleteObject:question];
|
||||||
|
[context saveToStore];
|
||||||
|
[self setMultiple:NO animated:YES];
|
||||||
|
}];
|
||||||
|
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
|
||||||
|
NSString *body;
|
||||||
|
if (!_multiple) {
|
||||||
|
NSObject *answer = [site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key];
|
||||||
|
body = strf( @"Master Password generated the following security answer for your site: %@\n\n"
|
||||||
|
@"%@\n"
|
||||||
|
@"\n\nYou should use this as the answer to each security question the site asks you.\n"
|
||||||
|
@"Do not share this answer with others!", site.name, answer );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
NSMutableString *bodyBuilder = [NSMutableString string];
|
||||||
|
[bodyBuilder appendFormat:@"Master Password generated the following security answers for your site: %@\n\n", site.name];
|
||||||
|
for (MPSiteQuestionEntity *question in site.questions) {
|
||||||
|
NSObject *answer = [site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key];
|
||||||
|
[bodyBuilder appendFormat:@"For question: '%@', use answer: %@\n", question.keyword, answer];
|
||||||
|
}
|
||||||
|
[bodyBuilder appendFormat:@"\n\nUse the answer for the matching security question.\n"
|
||||||
|
@"Do not share this answer with others!"];
|
||||||
|
body = bodyBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
|
||||||
|
}
|
||||||
|
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]]) {
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
|
||||||
|
[UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Private
|
||||||
|
|
||||||
|
- (void)updateAnimated:(BOOL)animated {
|
||||||
|
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
UITableViewCell *multipleAnswersCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]];
|
||||||
|
multipleAnswersCell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
|
||||||
|
|
||||||
|
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||||
|
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||||
|
}];
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)didAddQuestion:(MPSiteQuestionEntity *)question toSite:(MPSiteEntity *)site {
|
||||||
|
|
||||||
|
NSUInteger newQuestionRow = [site.questions count];
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
[self.tableView beginUpdates];
|
||||||
|
[self.tableView insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:newQuestionRow inSection:1] ]
|
||||||
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||||
|
[self.tableView endUpdates];
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPGlobalAnswersCell
|
||||||
|
|
||||||
|
#pragma mark - State
|
||||||
|
|
||||||
|
- (void)setSite:(MPSiteEntity *)site {
|
||||||
|
|
||||||
|
self.titleLabel.text = strl( @"Answer for %@:", site.name );
|
||||||
|
self.answerField.text = @"...";
|
||||||
|
[site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
self.answerField.text = result;
|
||||||
|
} );
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPSendAnswersCell
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPMultipleAnswersCell
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPAnswersQuestionCell {
|
||||||
|
NSManagedObjectID *_siteOID;
|
||||||
|
NSManagedObjectID *_questionOID;
|
||||||
|
__weak MPAnswersViewController *_answersVC;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - State
|
||||||
|
|
||||||
|
- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site inVC:(MPAnswersViewController *)answersVC {
|
||||||
|
|
||||||
|
_siteOID = site.objectID;
|
||||||
|
_questionOID = question.objectID;
|
||||||
|
_answersVC = answersVC;
|
||||||
|
|
||||||
|
[self updateAnswerForQuestion:question ofSite:site];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITextFieldDelegate
|
||||||
|
|
||||||
|
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||||
|
|
||||||
|
[textField resignFirstResponder];
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)textFieldDidChange:(UITextField *)textField {
|
||||||
|
|
||||||
|
NSString *keyword = textField.text;
|
||||||
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
BOOL didAddQuestionObject = NO;
|
||||||
|
MPSiteEntity *site = [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
|
||||||
|
MPSiteQuestionEntity *question = [MPSiteQuestionEntity existingObjectWithID:_questionOID inContext:context];
|
||||||
|
if (!question) {
|
||||||
|
didAddQuestionObject = YES;
|
||||||
|
[site addQuestionsObject:question = [MPSiteQuestionEntity insertNewObjectInContext:context]];
|
||||||
|
question.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
|
question.keyword = keyword;
|
||||||
|
|
||||||
|
if ([context saveToStore]) {
|
||||||
|
if ([question.objectID isTemporaryID]) {
|
||||||
|
NSError *error = nil;
|
||||||
|
[context obtainPermanentIDsForObjects:@[ question ] error:&error];
|
||||||
|
if (error)
|
||||||
|
err( @"Failed to obtain permanent object ID: %@", [error fullDescription] );
|
||||||
|
}
|
||||||
|
|
||||||
|
_questionOID = question.objectID;
|
||||||
|
[self updateAnswerForQuestion:question ofSite:site];
|
||||||
|
|
||||||
|
if (didAddQuestionObject)
|
||||||
|
[_answersVC didAddQuestion:question toSite:site];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Private
|
||||||
|
|
||||||
|
- (void)updateAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site {
|
||||||
|
|
||||||
|
if (!question)
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
self.questionField.text = self.answerField.text = nil;
|
||||||
|
} );
|
||||||
|
|
||||||
|
else {
|
||||||
|
NSString *keyword = question.keyword;
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
self.answerField.text = @"...";
|
||||||
|
} );
|
||||||
|
[site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
|
||||||
|
PearlMainQueue( ^{
|
||||||
|
self.questionField.text = keyword;
|
||||||
|
self.answerField.text = result;
|
||||||
|
} );
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -19,23 +19,6 @@
|
|||||||
#import "MPAppSettingsViewController.h"
|
#import "MPAppSettingsViewController.h"
|
||||||
#import "UIColor+Expanded.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 {
|
@implementation MPAppSettingsViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const long MPAvatarAdd = 10000;
|
|||||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarSizeConstraint;
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarSizeConstraint;
|
||||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarToTopConstraint;
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarToTopConstraint;
|
||||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarRaisedConstraint;
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarRaisedConstraint;
|
||||||
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *keyboardHeightConstraint;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -66,6 +67,12 @@ const long MPAvatarAdd = 10000;
|
|||||||
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *_self) {
|
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *_self) {
|
||||||
[_self updateAnimated:_self.superview != nil];
|
[_self updateAnimated:_self.superview != nil];
|
||||||
}];
|
}];
|
||||||
|
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPAvatarCell *self, NSNotification *note) {
|
||||||
|
CGRect keyboardRect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||||
|
CGFloat keyboardHeight = CGRectGetHeight( self.window.screen.bounds ) - CGRectGetMinY( keyboardRect );
|
||||||
|
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
|
||||||
|
} );
|
||||||
|
|
||||||
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||||
toShadowOpacityAnimation.toValue = @0.2f;
|
toShadowOpacityAnimation.toValue = @0.2f;
|
||||||
@@ -99,6 +106,7 @@ const long MPAvatarAdd = 10000;
|
|||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
|
|
||||||
[self removeKeyPathObservers];
|
[self removeKeyPathObservers];
|
||||||
|
PearlRemoveNotificationObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Properties
|
#pragma mark - Properties
|
||||||
@@ -214,18 +222,20 @@ const long MPAvatarAdd = 10000;
|
|||||||
|
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
case MPAvatarModeLowered: {
|
case MPAvatarModeLowered: {
|
||||||
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
|
[self.avatarSizeConstraint updateConstant:
|
||||||
|
self.avatarImageView.image.size.height * (self.visibility * 0.3f + 0.7f)];
|
||||||
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
self.nameContainer.alpha = self.visibility;
|
self.nameContainer.alpha = self.visibility;
|
||||||
self.nameContainer.backgroundColor = [UIColor clearColor];
|
self.nameContainer.backgroundColor = [UIColor clearColor];
|
||||||
self.avatarImageView.alpha = self.visibility / 0.7f + 0.3f;
|
self.avatarImageView.alpha = self.visibility * 0.7f + 0.3f;
|
||||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPAvatarModeRaisedButInactive: {
|
case MPAvatarModeRaisedButInactive: {
|
||||||
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
|
[self.avatarSizeConstraint updateConstant:
|
||||||
|
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
|
||||||
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
||||||
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
@@ -236,7 +246,8 @@ const long MPAvatarAdd = 10000;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPAvatarModeRaisedAndActive: {
|
case MPAvatarModeRaisedAndActive: {
|
||||||
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
|
[self.avatarSizeConstraint updateConstant:
|
||||||
|
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
|
||||||
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
||||||
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
||||||
@@ -247,7 +258,8 @@ const long MPAvatarAdd = 10000;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPAvatarModeRaisedAndHidden: {
|
case MPAvatarModeRaisedAndHidden: {
|
||||||
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
|
[self.avatarSizeConstraint updateConstant:
|
||||||
|
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
|
||||||
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
||||||
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
||||||
@@ -260,7 +272,7 @@ const long MPAvatarAdd = 10000;
|
|||||||
case MPAvatarModeRaisedAndMinimized: {
|
case MPAvatarModeRaisedAndMinimized: {
|
||||||
[self.avatarSizeConstraint updateConstant:36];
|
[self.avatarSizeConstraint updateConstant:36];
|
||||||
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
|
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
|
||||||
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultHigh + 2];
|
||||||
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
|
||||||
self.nameContainer.alpha = 0;
|
self.nameContainer.alpha = 0;
|
||||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#import "MPUsersViewController.h"
|
||||||
|
#import "MPPasswordsViewController.h"
|
||||||
|
#import "MPEmergencyViewController.h"
|
||||||
|
|
||||||
typedef NS_ENUM(NSUInteger, MPCombinedMode) {
|
typedef NS_ENUM(NSUInteger, MPCombinedMode) {
|
||||||
MPCombinedModeUserSelection,
|
MPCombinedModeUserSelection,
|
||||||
MPCombinedModePasswordSelection,
|
MPCombinedModePasswordSelection,
|
||||||
@@ -23,7 +27,9 @@ typedef NS_ENUM(NSUInteger, MPCombinedMode) {
|
|||||||
|
|
||||||
@interface MPCombinedViewController : UIViewController
|
@interface MPCombinedViewController : UIViewController
|
||||||
|
|
||||||
@property(assign, nonatomic) MPCombinedMode mode;
|
@property(nonatomic) MPCombinedMode mode;
|
||||||
@property(strong, nonatomic) IBOutlet UIView *usersView;
|
@property(nonatomic, weak) MPUsersViewController *usersVC;
|
||||||
|
@property(nonatomic, weak) MPPasswordsViewController *passwordsVC;
|
||||||
|
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -19,26 +19,19 @@
|
|||||||
#import "MPCombinedViewController.h"
|
#import "MPCombinedViewController.h"
|
||||||
#import "MPUsersViewController.h"
|
#import "MPUsersViewController.h"
|
||||||
#import "MPPasswordsViewController.h"
|
#import "MPPasswordsViewController.h"
|
||||||
#import "MPEmergencySegue.h"
|
|
||||||
#import "MPEmergencyViewController.h"
|
#import "MPEmergencyViewController.h"
|
||||||
#import "MPPasswordsSegue.h"
|
#import "MPPasswordsSegue.h"
|
||||||
|
|
||||||
@interface MPCombinedViewController()
|
@implementation MPCombinedViewController
|
||||||
|
|
||||||
@property(nonatomic, weak) MPUsersViewController *usersVC;
|
#pragma mark - Life
|
||||||
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation MPCombinedViewController {
|
|
||||||
NSArray *_notificationObservers;
|
|
||||||
MPPasswordsViewController *_passwordsVC;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidLoad {
|
- (void)viewDidLoad {
|
||||||
|
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
_mode = MPCombinedModeUserSelection;
|
_mode = MPCombinedModeUserSelection;
|
||||||
|
[self performSegueWithIdentifier:@"users" sender:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated {
|
- (void)viewWillAppear:(BOOL)animated {
|
||||||
@@ -52,14 +45,21 @@
|
|||||||
|
|
||||||
[super viewDidAppear:animated];
|
[super viewDidAppear:animated];
|
||||||
|
|
||||||
[self registerObservers];
|
PearlAddNotificationObserver( MPSignedInNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPCombinedViewController *self, NSNotification *note) {
|
||||||
|
[self setMode:MPCombinedModePasswordSelection];
|
||||||
|
} );
|
||||||
|
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPCombinedViewController *self, NSNotification *note) {
|
||||||
|
[self setMode:MPCombinedModeUserSelection animated:[note.userInfo[@"animated"] boolValue]];
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewWillDisappear:animated];
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
[self removeObservers];
|
PearlRemoveNotificationObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||||
@@ -99,20 +99,14 @@
|
|||||||
[self performSegueWithIdentifier:@"emergency" sender:self];
|
[self performSegueWithIdentifier:@"emergency" sender:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
|
#pragma mark - Actions
|
||||||
fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
|
|
||||||
|
|
||||||
if ([identifier isEqualToString:@"unwind-emergency"]) {
|
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
|
||||||
MPEmergencySegue *segue = [[MPEmergencySegue alloc] initWithIdentifier:identifier
|
|
||||||
source:fromViewController destination:toViewController];
|
dbg( @"unwindToCombined:%@", sender );
|
||||||
segue.unwind = YES;
|
|
||||||
dbg_return(segue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg_return((id)nil);
|
#pragma mark - State
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Properties
|
|
||||||
|
|
||||||
- (void)setMode:(MPCombinedMode)mode {
|
- (void)setMode:(MPCombinedMode)mode {
|
||||||
|
|
||||||
@@ -133,7 +127,7 @@
|
|||||||
|
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
case MPCombinedModeUserSelection: {
|
case MPCombinedModeUserSelection: {
|
||||||
self.usersView.userInteractionEnabled = YES;
|
self.usersVC.view.userInteractionEnabled = YES;
|
||||||
[self.usersVC setActive:YES animated:animated];
|
[self.usersVC setActive:YES animated:animated];
|
||||||
if (_passwordsVC) {
|
if (_passwordsVC) {
|
||||||
MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self];
|
MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self];
|
||||||
@@ -143,7 +137,7 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MPCombinedModePasswordSelection: {
|
case MPCombinedModePasswordSelection: {
|
||||||
self.usersView.userInteractionEnabled = NO;
|
self.usersVC.view.userInteractionEnabled = NO;
|
||||||
[self.usersVC setActive:NO animated:animated];
|
[self.usersVC setActive:NO animated:animated];
|
||||||
[self performSegueWithIdentifier:@"passwords" sender:@{ @"animated" : @(animated) }];
|
[self performSegueWithIdentifier:@"passwords" sender:@{ @"animated" : @(animated) }];
|
||||||
break;
|
break;
|
||||||
@@ -153,35 +147,4 @@
|
|||||||
|
|
||||||
#pragma mark - Private
|
#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
|
@end
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
|
||||||
*
|
|
||||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
|
||||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
|
||||||
*
|
|
||||||
* @author Maarten Billemont <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
|
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
@interface MPEmergencyViewController : UIViewController <UITextFieldDelegate>
|
@interface MPEmergencyViewController : UIViewController <UITextFieldDelegate>
|
||||||
|
|
||||||
|
@property(weak, nonatomic) IBOutlet UIScrollView *scrollView;
|
||||||
@property(weak, nonatomic) IBOutlet UIView *dialogView;
|
@property(weak, nonatomic) IBOutlet UIView *dialogView;
|
||||||
@property(weak, nonatomic) IBOutlet UIView *containerView;
|
@property(weak, nonatomic) IBOutlet UIView *containerView;
|
||||||
@property(weak, nonatomic) IBOutlet UITextField *userNameField;
|
@property(weak, nonatomic) IBOutlet UITextField *userNameField;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
MPKey *_key;
|
MPKey *_key;
|
||||||
NSOperationQueue *_emergencyKeyQueue;
|
NSOperationQueue *_emergencyKeyQueue;
|
||||||
NSOperationQueue *_emergencyPasswordQueue;
|
NSOperationQueue *_emergencyPasswordQueue;
|
||||||
NSArray *_notificationObservers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLoad {
|
- (void)viewDidLoad {
|
||||||
@@ -42,27 +41,26 @@
|
|||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
[self reset];
|
[self reset];
|
||||||
[self registerObservers];
|
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPEmergencyViewController *self, NSNotification *note) {
|
||||||
|
[self performSegueWithIdentifier:@"unwind-popover" sender:self];
|
||||||
|
} );
|
||||||
|
|
||||||
|
[self.scrollView automaticallyAdjustInsetsForKeyboard];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
- (void)viewDidDisappear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
|
|
||||||
[self removeObservers];
|
PearlRemoveNotificationObservers();
|
||||||
|
PearlRemoveNotificationObserversFrom( self.scrollView );
|
||||||
[self reset];
|
[self reset];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||||
|
|
||||||
return [self respondsToSelector:action];
|
return UIStatusBarStyleLightContent;
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Actions
|
|
||||||
|
|
||||||
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
|
|
||||||
|
|
||||||
dbg(@"unwindToCombined:%@", sender);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UITextFieldDelegate
|
#pragma mark - UITextFieldDelegate
|
||||||
@@ -126,7 +124,7 @@
|
|||||||
- (void)updatePassword {
|
- (void)updatePassword {
|
||||||
|
|
||||||
NSString *siteName = self.siteField.text;
|
NSString *siteName = self.siteField.text;
|
||||||
MPElementType siteType = [self siteType];
|
MPSiteType siteType = [self siteType];
|
||||||
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
|
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
|
||||||
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
|
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
|
||||||
|
|
||||||
@@ -145,21 +143,21 @@
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (enum MPElementType)siteType {
|
- (enum MPSiteType)siteType {
|
||||||
|
|
||||||
switch (self.typeControl.selectedSegmentIndex) {
|
switch (self.typeControl.selectedSegmentIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return MPElementTypeGeneratedMaximum;
|
return MPSiteTypeGeneratedMaximum;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
case 2:
|
case 2:
|
||||||
return MPElementTypeGeneratedMedium;
|
return MPSiteTypeGeneratedMedium;
|
||||||
case 3:
|
case 3:
|
||||||
return MPElementTypeGeneratedBasic;
|
return MPSiteTypeGeneratedBasic;
|
||||||
case 4:
|
case 4:
|
||||||
return MPElementTypeGeneratedShort;
|
return MPSiteTypeGeneratedShort;
|
||||||
case 5:
|
case 5:
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPSiteTypeGeneratedPIN;
|
||||||
default:
|
default:
|
||||||
Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex );
|
Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex );
|
||||||
}
|
}
|
||||||
@@ -175,28 +173,4 @@
|
|||||||
[self updateKey];
|
[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
|
@end
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPGuideViewController.h"
|
#import "MPGuideViewController.h"
|
||||||
|
#import "markdown_lib.h"
|
||||||
|
#import "NSString+MPMarkDown.h"
|
||||||
|
|
||||||
@interface MPGuideStep : NSObject
|
@interface MPGuideStep : NSObject
|
||||||
|
|
||||||
@@ -37,28 +39,50 @@
|
|||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
self.steps = @[
|
self.steps = @[
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-0"] caption:
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"initial"] caption:
|
||||||
@"To begin, tap the \"New User\" icon and add yourself as a user to the application."],
|
@"To begin, tap the *New User* icon and add yourself as a user to the application."],
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-1"] caption:
|
|
||||||
@"Enter your full name. Double-check that you have spelled your name correctly and capitalized it appropriately. Your passwords will depend on it."],
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"name_new"] caption:
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-2"] caption:
|
@"Enter your full name. \n"
|
||||||
@"Choose a master password: Use something new and long. A short sentence is ideal.\nDO NOT FORGET THIS ONE PASSWORD."],
|
@"**Double-check** that you have spelled your name correctly and capitalized it appropriately. \n"
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-3"] caption:
|
@"Your passwords will depend on it."],
|
||||||
@"After logging in, you'll see an empty screen with a search box.\nTap the search box to begin adding sites."],
|
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-4"] caption:
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"mpw_new"] caption:
|
||||||
@"To add a site, just enter its name fully and tap the result. Names can be anything, but we recommend using a site's bare domain name."],
|
@"Choose a master password: Make it *new* and *long*. \n"
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-5"] caption:
|
@"A short phrase makes a great password. \n"
|
||||||
@"Your sites are easy to find and sorted by recency.\nTap any site to copy its password.\nYou can now switch and paste it in another app."],
|
@"**DO NOT FORGET THIS ONE PASSWORD**."],
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-6"] caption:
|
|
||||||
@"The user icon lets you save your site's login.\nThis is useful if you find it hard to remember the user name for this site."],
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"login_new"] caption:
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-7"] caption:
|
@"After logging in, you'll see an empty screen with a search box. \n"
|
||||||
|
@"Tap the search box to begin adding sites."],
|
||||||
|
|
||||||
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"site_new"] caption:
|
||||||
|
@"To add a site, just enter its name and tap the result. \n"
|
||||||
|
@"*We recommend* always using a site's **bare** domain name: eg. *apple.com*. \n"
|
||||||
|
@"(NOT *www.*apple.com or *store.*apple.com)"],
|
||||||
|
|
||||||
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"copy_pw"] caption:
|
||||||
|
@"Tap any site to copy its password. \n"
|
||||||
|
@"The first time, change your site's old password into this new one."],
|
||||||
|
|
||||||
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"settings"] caption:
|
||||||
@"To make changes to the site password, tap the settings icon or swipe left to reveal extra buttons."],
|
@"To make changes to the site password, tap the settings icon or swipe left to reveal extra buttons."],
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-8"] caption:
|
|
||||||
@"If you ever need a new password for the site, just tap the plus icon to increment its counter.\nYou can hold down to reset it back to 1."],
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"login_name"] caption:
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-9"] caption:
|
@"You can save the login name for the site. \n"
|
||||||
@"Use the list icon to upgrade or downgrade your password's complexity.\nSome sites won't let you use complex passwords."],
|
@"This is useful if you find it hard to remember your user name for this site."],
|
||||||
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-10"] caption:
|
|
||||||
@"If you have a password that you cannot change, you can save it as a Personal password. Device Private means the site will not be backed up."],
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"counter"] caption:
|
||||||
|
@"If you ever need a new password for the site, just tap the plus icon to increment its counter. \n"
|
||||||
|
@"You can hold down to reset it back to 1."],
|
||||||
|
|
||||||
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"choose_type"] caption:
|
||||||
|
@"Use the list icon to upgrade or downgrade your password's complexity. \n"
|
||||||
|
@"Some sites won't let you use complex passwords."],
|
||||||
|
|
||||||
|
[MPGuideStep stepWithImage:[UIImage imageNamed:@"personal_pw"] caption:
|
||||||
|
@"If you have a password that you cannot change, you can save it as a *personal* password. "
|
||||||
|
@"*Device private* means the site will not be backed up."],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +93,8 @@
|
|||||||
[self.pageControl observeKeyPath:@"currentPage"
|
[self.pageControl observeKeyPath:@"currentPage"
|
||||||
withBlock:^(id from, id to, NSKeyValueChange cause, UIPageControl *pageControl) {
|
withBlock:^(id from, id to, NSKeyValueChange cause, UIPageControl *pageControl) {
|
||||||
MPGuideStep *activeStep = self.steps[pageControl.currentPage];
|
MPGuideStep *activeStep = self.steps[pageControl.currentPage];
|
||||||
self.captionLabel.text = activeStep.caption;
|
self.captionLabel.attributedText =
|
||||||
|
[activeStep.caption attributedMarkdownStringWithFontSize:self.captionLabel.font.pointSize];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[self.collectionView setContentOffset:CGPointZero];
|
[self.collectionView setContentOffset:CGPointZero];
|
||||||
@@ -117,6 +142,7 @@
|
|||||||
|
|
||||||
MPGuideStepCell *cell = [MPGuideStepCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
MPGuideStepCell *cell = [MPGuideStepCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
||||||
cell.imageView.image = ((MPGuideStep *)self.steps[indexPath.item]).image;
|
cell.imageView.image = ((MPGuideStep *)self.steps[indexPath.item]).image;
|
||||||
|
cell.contentView.frame = cell.bounds;
|
||||||
|
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,18 +27,17 @@
|
|||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
self.view.backgroundColor = [UIColor clearColor];
|
self.view.backgroundColor = [UIColor clearColor];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil
|
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
|
||||||
^(NSNotification *note) {
|
|
||||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated {
|
- (void)viewWillAppear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPLogsViewController *self, NSNotification *note) {
|
||||||
|
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||||
|
} );
|
||||||
|
|
||||||
self.logView.contentInset = UIEdgeInsetsMake( 64, 0, 93, 0 );
|
self.logView.contentInset = UIEdgeInsetsMake( 64, 0, 93, 0 );
|
||||||
|
|
||||||
[self refresh:nil];
|
[self refresh:nil];
|
||||||
@@ -46,6 +45,13 @@
|
|||||||
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
|
PearlRemoveNotificationObservers();
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)toggleLevelControl:(UISegmentedControl *)sender {
|
- (IBAction)toggleLevelControl:(UISegmentedControl *)sender {
|
||||||
|
|
||||||
BOOL traceEnabled = (BOOL)self.levelControl.selectedSegmentIndex;
|
BOOL traceEnabled = (BOOL)self.levelControl.selectedSegmentIndex;
|
||||||
|
|||||||
29
MasterPassword/ObjC/iOS/MPOverlayViewController.h
Normal file
29
MasterPassword/ObjC/iOS/MPOverlayViewController.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
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPOverlayViewController.h
|
||||||
|
// MPOverlayViewController
|
||||||
|
//
|
||||||
|
// Created by lhunath on 2014-09-22.
|
||||||
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
|
||||||
|
@interface MPOverlayViewController : UIViewController
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPOverlaySegue : UIStoryboardSegue
|
||||||
|
|
||||||
|
+ (instancetype)dismissViewController:(UIViewController *)viewController;
|
||||||
|
|
||||||
|
@end
|
||||||
162
MasterPassword/ObjC/iOS/MPOverlayViewController.m
Normal file
162
MasterPassword/ObjC/iOS/MPOverlayViewController.m
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPOverlayViewController.h
|
||||||
|
// MPOverlayViewController
|
||||||
|
//
|
||||||
|
// Created by lhunath on 2014-09-22.
|
||||||
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPOverlayViewController.h"
|
||||||
|
|
||||||
|
@implementation MPOverlayViewController {
|
||||||
|
NSMutableDictionary *_dismissSegueByButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)awakeFromNib {
|
||||||
|
|
||||||
|
[super awakeFromNib];
|
||||||
|
|
||||||
|
_dismissSegueByButton = [NSMutableDictionary dictionary];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
[self performSegueWithIdentifier:@"root" sender:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIViewController *)childViewControllerForStatusBarStyle {
|
||||||
|
|
||||||
|
return [self.childViewControllers lastObject];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIViewController *)childViewControllerForStatusBarHidden {
|
||||||
|
|
||||||
|
return self.childViewControllerForStatusBarStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
|
||||||
|
fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
|
||||||
|
|
||||||
|
return [[MPOverlaySegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIView *)addDismissButtonForSegue:(MPOverlaySegue *)segue {
|
||||||
|
|
||||||
|
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[dismissButton addTarget:self action:@selector( dismissOverlay: ) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
dismissButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5f];
|
||||||
|
dismissButton.alpha = 0;
|
||||||
|
dismissButton.frame = self.view.bounds;
|
||||||
|
dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
|
_dismissSegueByButton[[NSValue valueWithNonretainedObject:dismissButton]] =
|
||||||
|
[[MPOverlaySegue alloc] initWithIdentifier:@"dismiss-overlay"
|
||||||
|
source:segue.destinationViewController destination:segue.sourceViewController];
|
||||||
|
|
||||||
|
[self.view addSubview:dismissButton];
|
||||||
|
return dismissButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dismissOverlay:(UIButton *)dismissButton {
|
||||||
|
|
||||||
|
NSValue *dismissSegueKey = [NSValue valueWithNonretainedObject:dismissButton];
|
||||||
|
[((UIStoryboardSegue *)_dismissSegueByButton[dismissSegueKey]) perform];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeDismissButtonForViewController:(UIViewController *)viewController {
|
||||||
|
|
||||||
|
UIButton *dismissButton = nil;
|
||||||
|
for (NSValue *dismissButtonValue in [_dismissSegueByButton allKeys])
|
||||||
|
if (((UIStoryboardSegue *)_dismissSegueByButton[dismissButtonValue]).sourceViewController == viewController) {
|
||||||
|
dismissButton = [dismissButtonValue nonretainedObjectValue];
|
||||||
|
NSAssert([self.view.subviews containsObject:dismissButton], @"Missing dismiss button in dictionary.");
|
||||||
|
}
|
||||||
|
if (!dismissButton)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NSValue *dismissSegueKey = [NSValue valueWithNonretainedObject:dismissButton];
|
||||||
|
[_dismissSegueByButton removeObjectForKey:dismissSegueKey];
|
||||||
|
|
||||||
|
[UIView animateWithDuration:0.1f animations:^{
|
||||||
|
dismissButton.alpha = 0;
|
||||||
|
} completion:^(BOOL finished) {
|
||||||
|
[dismissButton removeFromSuperview];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPOverlaySegue
|
||||||
|
|
||||||
|
+ (instancetype)dismissViewController:(UIViewController *)viewController {
|
||||||
|
|
||||||
|
return [[self alloc] initWithIdentifier:nil source:viewController destination:viewController];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)perform {
|
||||||
|
|
||||||
|
UIViewController *sourceViewController = self.sourceViewController;
|
||||||
|
UIViewController *destinationViewController = self.destinationViewController;
|
||||||
|
MPOverlayViewController *containerViewController = self.sourceViewController;
|
||||||
|
while (containerViewController && ![(id)containerViewController isKindOfClass:[MPOverlayViewController class]])
|
||||||
|
containerViewController = (id)containerViewController.parentViewController;
|
||||||
|
NSAssert( [containerViewController isKindOfClass:[MPOverlayViewController class]],
|
||||||
|
@"Not an overlay container: %@", containerViewController );
|
||||||
|
|
||||||
|
if (!destinationViewController.parentViewController) {
|
||||||
|
// Winding
|
||||||
|
[containerViewController addChildViewController:destinationViewController];
|
||||||
|
UIView *dismissButton = [containerViewController addDismissButtonForSegue:self];
|
||||||
|
|
||||||
|
destinationViewController.view.frame = containerViewController.view.bounds;
|
||||||
|
destinationViewController.view.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
|
[containerViewController.view addSubview:destinationViewController.view];
|
||||||
|
[containerViewController setNeedsStatusBarAppearanceUpdate];
|
||||||
|
|
||||||
|
CGRectSetY( destinationViewController.view.frame, 100 );
|
||||||
|
destinationViewController.view.transform = CGAffineTransformMakeScale( 1.2f, 1.2f );
|
||||||
|
destinationViewController.view.alpha = 0;
|
||||||
|
|
||||||
|
[UIView transitionWithView:containerViewController.view duration:0.3f
|
||||||
|
options:UIViewAnimationOptionAllowAnimatedContent animations:^{
|
||||||
|
destinationViewController.view.transform = CGAffineTransformIdentity;
|
||||||
|
CGRectSetY( destinationViewController.view.frame, 0 );
|
||||||
|
destinationViewController.view.alpha = 1;
|
||||||
|
dismissButton.alpha = 1;
|
||||||
|
} completion:^(BOOL finished) {
|
||||||
|
[destinationViewController didMoveToParentViewController:containerViewController];
|
||||||
|
[containerViewController setNeedsStatusBarAppearanceUpdate];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unwinding
|
||||||
|
[sourceViewController willMoveToParentViewController:nil];
|
||||||
|
[UIView transitionWithView:containerViewController.view duration:0.2f
|
||||||
|
options:UIViewAnimationOptionAllowAnimatedContent animations:^{
|
||||||
|
CGRectSetY( sourceViewController.view.frame, 100 );
|
||||||
|
sourceViewController.view.transform = CGAffineTransformMakeScale( 0.8f, 0.8f );
|
||||||
|
sourceViewController.view.alpha = 0;
|
||||||
|
[containerViewController removeDismissButtonForViewController:sourceViewController];
|
||||||
|
} completion:^(BOOL finished) {
|
||||||
|
if (finished) {
|
||||||
|
[sourceViewController.view removeFromSuperview];
|
||||||
|
[sourceViewController removeFromParentViewController];
|
||||||
|
[containerViewController setNeedsStatusBarAppearanceUpdate];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -27,8 +27,9 @@ typedef NS_ENUM ( NSUInteger, MPPasswordCellMode ) {
|
|||||||
|
|
||||||
@interface MPPasswordCell : MPCell <UIScrollViewDelegate, UITextFieldDelegate>
|
@interface MPPasswordCell : MPCell <UIScrollViewDelegate, UITextFieldDelegate>
|
||||||
|
|
||||||
- (void)setElement:(MPElementEntity *)element animated:(BOOL)animated;
|
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated;
|
||||||
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated;
|
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated;
|
||||||
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated;
|
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated;
|
||||||
|
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#import "MPiOSAppDelegate.h"
|
#import "MPiOSAppDelegate.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "UIColor+Expanded.h"
|
#import "UIColor+Expanded.h"
|
||||||
|
#import "MPAppDelegate_InApp.h"
|
||||||
|
|
||||||
@interface MPPasswordCell()
|
@interface MPPasswordCell()
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
@property(nonatomic, strong) IBOutlet UILabel *counterLabel;
|
@property(nonatomic, strong) IBOutlet UILabel *counterLabel;
|
||||||
@property(nonatomic, strong) IBOutlet UIButton *counterButton;
|
@property(nonatomic, strong) IBOutlet UIButton *counterButton;
|
||||||
@property(nonatomic, strong) IBOutlet UIButton *upgradeButton;
|
@property(nonatomic, strong) IBOutlet UIButton *upgradeButton;
|
||||||
|
@property(nonatomic, strong) IBOutlet UIButton *answersButton;
|
||||||
@property(nonatomic, strong) IBOutlet UIButton *modeButton;
|
@property(nonatomic, strong) IBOutlet UIButton *modeButton;
|
||||||
@property(nonatomic, strong) IBOutlet UIButton *editButton;
|
@property(nonatomic, strong) IBOutlet UIButton *editButton;
|
||||||
@property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView;
|
@property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView;
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPPasswordCell {
|
@implementation MPPasswordCell {
|
||||||
NSManagedObjectID *_elementOID;
|
NSManagedObjectID *_siteOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Life cycle
|
#pragma mark - Life cycle
|
||||||
@@ -60,11 +62,10 @@
|
|||||||
|
|
||||||
[self setupLayer];
|
[self setupLayer];
|
||||||
|
|
||||||
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPPasswordCell *_self) {
|
||||||
if (from && !CGSizeEqualToSize( [from CGRectValue].size, [to CGRectValue].size ))
|
if (from && !CGSizeEqualToSize( [from CGRectValue].size, [to CGRectValue].size ))
|
||||||
[_self setupLayer];
|
[_self setupLayer];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[self.contentButton observeKeyPath:@"highlighted"
|
[self.contentButton observeKeyPath:@"highlighted"
|
||||||
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
|
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
|
||||||
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||||
@@ -131,7 +132,7 @@
|
|||||||
|
|
||||||
[super prepareForReuse];
|
[super prepareForReuse];
|
||||||
|
|
||||||
_elementOID = nil;
|
_siteOID = nil;
|
||||||
self.transientSite = nil;
|
self.transientSite = nil;
|
||||||
self.mode = MPPasswordCellModePassword;
|
self.mode = MPPasswordCellModePassword;
|
||||||
[self updateAnimated:NO];
|
[self updateAnimated:NO];
|
||||||
@@ -139,6 +140,7 @@
|
|||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
|
|
||||||
|
[self removeKeyPathObservers];
|
||||||
[self.contentButton removeKeyPathObservers];
|
[self.contentButton removeKeyPathObservers];
|
||||||
[self.loginNameButton removeKeyPathObservers];
|
[self.loginNameButton removeKeyPathObservers];
|
||||||
}
|
}
|
||||||
@@ -154,9 +156,9 @@
|
|||||||
[self updateAnimated:animated];
|
[self updateAnimated:animated];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setElement:(MPElementEntity *)element animated:(BOOL)animated {
|
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated {
|
||||||
|
|
||||||
_elementOID = [element objectID];
|
_siteOID = [site objectID];
|
||||||
[self updateAnimated:animated];
|
[self updateAnimated:animated];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,10 +196,10 @@
|
|||||||
NSString *password = self.passwordField.text;
|
NSString *password = self.passwordField.text;
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
TimeToCrack timeToCrack;
|
TimeToCrack timeToCrack;
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
id<MPAlgorithm> algorithm = element.algorithm?: MPAlgorithmDefault;
|
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
|
||||||
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
|
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
|
||||||
if ([algorithm timeToCrack:&timeToCrack passwordOfType:[self elementInContext:context].type byAttacker:attackHardware] ||
|
if ([algorithm timeToCrack:&timeToCrack passwordOfType:[self siteInContext:context].type byAttacker:attackHardware] ||
|
||||||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
self.strengthLabel.text = NSStringFromTimeToCrack( timeToCrack );
|
self.strengthLabel.text = NSStringFromTimeToCrack( timeToCrack );
|
||||||
@@ -213,24 +215,27 @@
|
|||||||
NSString *text = textField.text;
|
NSString *text = textField.text;
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
if (!element)
|
if (!site)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (textField == self.passwordField) {
|
if (textField == self.passwordField) {
|
||||||
if ([element.algorithm savePassword:text toElement:element usingKey:[MPiOSAppDelegate get].key])
|
if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key])
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
|
||||||
}
|
}
|
||||||
else if (textField == self.loginNameField &&
|
else if (textField == self.loginNameField &&
|
||||||
((element.loginGenerated && ![text length]) ||
|
((site.loginGenerated && ![text length]) ||
|
||||||
(!element.loginGenerated && ![text isEqualToString:element.loginName]))) {
|
(!site.loginGenerated && ![text isEqualToString:site.loginName]))) {
|
||||||
element.loginName = text;
|
if (site.loginGenerated || !([site.loginName isEqualToString:text] || (!text && !site.loginName))) {
|
||||||
element.loginGenerated = NO;
|
site.loginGenerated = NO;
|
||||||
|
site.loginName = text;
|
||||||
|
|
||||||
if ([text length])
|
if ([text length])
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2];
|
||||||
else
|
else
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
[self updateAnimated:YES];
|
[self updateAnimated:YES];
|
||||||
@@ -242,17 +247,17 @@
|
|||||||
|
|
||||||
- (IBAction)doDelete:(UIButton *)sender {
|
- (IBAction)doDelete:(UIButton *)sender {
|
||||||
|
|
||||||
MPElementEntity *element = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
if (!element)
|
if (!site)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[PearlSheet showSheetWithTitle:strf( @"Delete %@?", element.name ) viewStyle:UIActionSheetStyleAutomatic
|
[PearlSheet showSheetWithTitle:strf( @"Delete %@?", site.name ) viewStyle:UIActionSheetStyleAutomatic
|
||||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||||
if (buttonIndex == [sheet cancelButtonIndex])
|
if (buttonIndex == [sheet cancelButtonIndex])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[context deleteObject:[self elementInContext:context]];
|
[context deleteObject:[self siteInContext:context]];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
}];
|
}];
|
||||||
} cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil];
|
} cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil];
|
||||||
@@ -264,12 +269,11 @@
|
|||||||
|
|
||||||
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
|
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
|
||||||
initSheet:^(UIActionSheet *sheet) {
|
initSheet:^(UIActionSheet *sheet) {
|
||||||
MPElementEntity
|
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
*mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
|
||||||
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
|
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
|
||||||
MPElementType type = [typeNumber unsignedIntegerValue];
|
MPSiteType type = [typeNumber unsignedIntegerValue];
|
||||||
NSString *typeName = [MPAlgorithmDefault nameOfType:type];
|
NSString *typeName = [MPAlgorithmDefault nameOfType:type];
|
||||||
if (type == mainElement.type)
|
if (type == mainSite.type)
|
||||||
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
|
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
|
||||||
else
|
else
|
||||||
[sheet addButtonWithTitle:typeName];
|
[sheet addButtonWithTitle:typeName];
|
||||||
@@ -278,12 +282,12 @@
|
|||||||
if (buttonIndex == [sheet cancelButtonIndex])
|
if (buttonIndex == [sheet cancelButtonIndex])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MPElementType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPElementTypeGeneratedLong;
|
MPSiteType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
element = [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:type];
|
site = [[MPiOSAppDelegate get] changeSite:site saveInContext:context toType:type];
|
||||||
[self setElement:element animated:YES];
|
[self setSite:site animated:YES];
|
||||||
}];
|
}];
|
||||||
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil];
|
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil];
|
||||||
}
|
}
|
||||||
@@ -293,7 +297,7 @@
|
|||||||
self.loginNameField.enabled = YES;
|
self.loginNameField.enabled = YES;
|
||||||
self.passwordField.enabled = YES;
|
self.passwordField.enabled = YES;
|
||||||
|
|
||||||
if ([self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPElementTypeClassStored)
|
if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPSiteTypeClassStored)
|
||||||
[self.passwordField becomeFirstResponder];
|
[self.passwordField becomeFirstResponder];
|
||||||
else
|
else
|
||||||
[self.loginNameField becomeFirstResponder];
|
[self.loginNameField becomeFirstResponder];
|
||||||
@@ -309,14 +313,12 @@
|
|||||||
[self setMode:MPPasswordCellModePassword animated:YES];
|
[self setMode:MPPasswordCellModePassword animated:YES];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self updateAnimated:YES];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)doUpgrade:(UIButton *)sender {
|
- (IBAction)doUpgrade:(UIButton *)sender {
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
if (![[self elementInContext:context] migrateExplicitly:YES]) {
|
if (![[self siteInContext:context] tryMigrateExplicitly:YES]) {
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -330,11 +332,11 @@
|
|||||||
- (IBAction)doIncrementCounter:(UIButton *)sender {
|
- (IBAction)doIncrementCounter:(UIButton *)sender {
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
if (!element || ![element isKindOfClass:[MPElementGeneratedEntity class]])
|
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
++((MPElementGeneratedEntity *)element).counter;
|
++((MPGeneratedSiteEntity *)site).counter;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Generating New Password" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Generating New Password" dismissAfter:2];
|
||||||
@@ -362,11 +364,11 @@
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
if (!element || ![element isKindOfClass:[MPElementGeneratedEntity class]])
|
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
((MPElementGeneratedEntity *)element).counter = 1;
|
((MPGeneratedSiteEntity *)site).counter = 1;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2];
|
||||||
@@ -392,8 +394,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[MPiOSAppDelegate get]
|
[[MPiOSAppDelegate get]
|
||||||
addElementNamed:self.transientSite completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
|
addSiteNamed:self.transientSite completion:^(MPSiteEntity *site, NSManagedObjectContext *context) {
|
||||||
[self copyContentOfElement:element saveInContext:context];
|
[self copyContentOfSite:site saveInContext:context];
|
||||||
|
|
||||||
PearlMainQueueAfter( .3f, ^{
|
PearlMainQueueAfter( .3f, ^{
|
||||||
[UIView animateWithDuration:.2f animations:^{
|
[UIView animateWithDuration:.2f animations:^{
|
||||||
@@ -406,7 +408,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
[self copyContentOfElement:[self elementInContext:context] saveInContext:context];
|
[self copyContentOfSite:[self siteInContext:context] saveInContext:context];
|
||||||
|
|
||||||
PearlMainQueueAfter( .3f, ^{
|
PearlMainQueueAfter( .3f, ^{
|
||||||
[UIView animateWithDuration:.2f animations:^{
|
[UIView animateWithDuration:.2f animations:^{
|
||||||
@@ -423,9 +425,9 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
if (![self copyLoginOfElement:element saveInContext:context]) {
|
if (![self copyLoginOfSite:site saveInContext:context]) {
|
||||||
element.loginGenerated = YES;
|
site.loginGenerated = YES;
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Generated" dismissAfter:2];
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Generated" dismissAfter:2];
|
||||||
[self updateAnimated:YES];
|
[self updateAnimated:YES];
|
||||||
@@ -461,15 +463,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UIView animateWithDuration:animated? .3f: 0 animations:^{
|
[UIView animateWithDuration:animated? .3f: 0 animations:^{
|
||||||
MPElementEntity *mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
self.upgradeButton.alpha = mainElement.requiresExplicitMigration? 1: 0;
|
self.upgradeButton.gone = !mainSite.requiresExplicitMigration;
|
||||||
|
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
|
||||||
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
|
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
|
||||||
self.loginNameContainer.alpha = settingsMode || mainElement.loginGenerated || [mainElement.loginName length]? 0.7f: 0;
|
self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0;
|
||||||
self.loginNameField.textColor = [UIColor colorWithHexString:mainElement.loginGenerated? @"5E636D": @"6D5E63"];
|
self.loginNameField.textColor = [UIColor colorWithHexString:mainSite.loginGenerated? @"5E636D": @"6D5E63"];
|
||||||
self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f;
|
self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f;
|
||||||
self.counterLabel.alpha = self.counterButton.alpha = mainElement.type & MPElementTypeClassGenerated? 0.5f: 0;
|
self.counterLabel.alpha = self.counterButton.alpha = mainSite.type & MPSiteTypeClassGenerated? 0.5f: 0;
|
||||||
self.modeButton.selected = settingsMode;
|
self.modeButton.selected = settingsMode;
|
||||||
self.strengthLabel.gone = !settingsMode;
|
self.strengthLabel.gone = !settingsMode;
|
||||||
self.modeScrollView.scrollEnabled = !self.transientSite;
|
self.modeScrollView.scrollEnabled = !self.transientSite;
|
||||||
@@ -478,36 +481,43 @@
|
|||||||
[self.loginNameField resignFirstResponder];
|
[self.loginNameField resignFirstResponder];
|
||||||
[self.passwordField resignFirstResponder];
|
[self.passwordField resignFirstResponder];
|
||||||
}
|
}
|
||||||
|
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
|
||||||
|
[self.loginNameButton setTitle:@"Tap to generate username or use pencil to save one" forState:UIControlStateNormal];
|
||||||
|
else
|
||||||
|
[self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal];
|
||||||
|
|
||||||
// Site Name
|
// Site Name
|
||||||
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainElement.name,
|
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainSite.name,
|
||||||
self.transientSite? @"Tap to create": [mainElement.algorithm shortNameOfType:mainElement.type] );
|
self.transientSite? @"Tap to create": [mainSite.algorithm shortNameOfType:mainSite.type] );
|
||||||
|
|
||||||
// Site Password
|
// Site Password
|
||||||
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
|
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
|
||||||
self.passwordField.attributedPlaceholder = stra(
|
self.passwordField.attributedPlaceholder = stra(
|
||||||
mainElement.type & MPElementTypeClassStored? strl( @"No password" ):
|
mainSite.type & MPSiteTypeClassStored? strl( @"No password" ):
|
||||||
mainElement.type & MPElementTypeClassGenerated? strl( @"..." ): @"", @{
|
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
|
||||||
NSForegroundColorAttributeName : [UIColor whiteColor]
|
NSForegroundColorAttributeName : [UIColor whiteColor]
|
||||||
} );
|
} );
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementEntity *element = [self elementInContext:context];
|
MPSiteEntity *site = [self siteInContext:context];
|
||||||
MPKey *key = [MPiOSAppDelegate get].key;
|
MPKey *key = [MPiOSAppDelegate get].key;
|
||||||
NSString *password, *loginName = [element resolveLoginUsingKey:key];
|
if (!key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NSString *password, *loginName = [site resolveLoginUsingKey:key];
|
||||||
if (self.transientSite)
|
if (self.transientSite)
|
||||||
password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:
|
password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:
|
||||||
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPElementTypeGeneratedLong
|
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPSiteTypeGeneratedLong
|
||||||
withCounter:1 usingKey:key];
|
withCounter:1 usingKey:key];
|
||||||
else if (element)
|
else if (site)
|
||||||
password = [element resolvePasswordUsingKey:key];
|
password = [site resolvePasswordUsingKey:key];
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TimeToCrack timeToCrack;
|
TimeToCrack timeToCrack;
|
||||||
NSString *timeToCrackString = nil;
|
NSString *timeToCrackString = nil;
|
||||||
id<MPAlgorithm> algorithm = element.algorithm?: MPAlgorithmDefault;
|
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
|
||||||
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
|
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
|
||||||
if ([algorithm timeToCrack:&timeToCrack passwordOfType:element.type byAttacker:attackHardware] ||
|
if ([algorithm timeToCrack:&timeToCrack passwordOfType:site.type byAttacker:attackHardware] ||
|
||||||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
|
||||||
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
|
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
|
||||||
|
|
||||||
@@ -533,8 +543,8 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
// Site Counter
|
// Site Counter
|
||||||
if ([mainElement isKindOfClass:[MPElementGeneratedEntity class]])
|
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPElementGeneratedEntity *)mainElement).counter );
|
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
|
||||||
|
|
||||||
// Site Login Name
|
// Site Login Name
|
||||||
self.loginNameField.enabled = self.passwordField.enabled = //
|
self.loginNameField.enabled = self.passwordField.enabled = //
|
||||||
@@ -544,10 +554,10 @@
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)copyContentOfElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
|
- (BOOL)copyContentOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
inf( @"Copying password for: %@", element.name );
|
inf( @"Copying password for: %@", site.name );
|
||||||
NSString *password = [element resolvePasswordUsingKey:[MPAppDelegate_Shared get].key];
|
NSString *password = [site resolvePasswordUsingKey:[MPAppDelegate_Shared get].key];
|
||||||
if (![password length])
|
if (![password length])
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
@@ -556,15 +566,15 @@
|
|||||||
[UIPasteboard generalPasteboard].string = password;
|
[UIPasteboard generalPasteboard].string = password;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
[element use];
|
[site use];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)copyLoginOfElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
|
- (BOOL)copyLoginOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
inf( @"Copying login for: %@", element.name );
|
inf( @"Copying login for: %@", site.name );
|
||||||
NSString *loginName = [element.algorithm resolveLoginForElement:element usingKey:[MPiOSAppDelegate get].key];
|
NSString *loginName = [site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key];
|
||||||
if (![loginName length])
|
if (![loginName length])
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
@@ -573,14 +583,14 @@
|
|||||||
[UIPasteboard generalPasteboard].string = loginName;
|
[UIPasteboard generalPasteboard].string = loginName;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
[element use];
|
[site use];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
|
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
|
||||||
|
|
||||||
return [MPElementEntity existingObjectWithID:_elementOID inContext:context];
|
return [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -42,11 +42,9 @@
|
|||||||
passwordsVC.active = NO;
|
passwordsVC.active = NO;
|
||||||
|
|
||||||
UIView *passwordsView = passwordsVC.view;
|
UIView *passwordsView = passwordsVC.view;
|
||||||
passwordsView.translatesAutoresizingMaskIntoConstraints = NO;
|
passwordsView.frame = combinedVC.view.bounds;
|
||||||
[combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersView];
|
passwordsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
[combinedVC.view addConstraintsWithVisualFormats:@[ @"H:|[passwordsView]|", @"V:|[passwordsView]|" ]
|
[combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersVC.view];
|
||||||
options:0 metrics:nil
|
|
||||||
views:NSDictionaryOfVariableBindings( passwordsView )];
|
|
||||||
|
|
||||||
[passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) {
|
[passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) {
|
||||||
if (!finished)
|
if (!finished)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
@class MPElementEntity;
|
@class MPSiteEntity;
|
||||||
@class MPCoachmark;
|
@class MPCoachmark;
|
||||||
|
|
||||||
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
|
||||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
|
||||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
|
||||||
|
@property(strong, nonatomic) IBOutlet UIView *badNameTipContainer;
|
||||||
@property(strong, nonatomic) IBOutlet UIView *popdownView;
|
@property(strong, nonatomic) IBOutlet UIView *popdownView;
|
||||||
@property(strong, nonatomic) IBOutlet UIView *popdownContainer;
|
@property(strong, nonatomic) IBOutlet UIView *popdownContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,11 @@
|
|||||||
#import "MPPopdownSegue.h"
|
#import "MPPopdownSegue.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
#import "MPPasswordCell.h"
|
#import "MPPasswordCell.h"
|
||||||
#import "UICollectionView+PearlReloadFromArray.h"
|
#import "MPAnswersViewController.h"
|
||||||
|
|
||||||
|
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||||
|
MPPasswordsBadNameTip = 1 << 0,
|
||||||
|
};
|
||||||
|
|
||||||
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
|
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
|
||||||
|
|
||||||
@@ -32,10 +36,6 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPPasswordsViewController {
|
@implementation MPPasswordsViewController {
|
||||||
__weak id _storeChangingObserver;
|
|
||||||
__weak id _storeChangedObserver;
|
|
||||||
__weak id _mocObserver;
|
|
||||||
NSArray *_notificationObservers;
|
|
||||||
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
|
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
|
||||||
NSFetchedResultsController *_fetchedResultsController;
|
NSFetchedResultsController *_fetchedResultsController;
|
||||||
UIColor *_backgroundColor;
|
UIColor *_backgroundColor;
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
__weak UIViewController *_popdownVC;
|
__weak UIViewController *_popdownVC;
|
||||||
BOOL _showTransientItem;
|
BOOL _showTransientItem;
|
||||||
NSUInteger _transientItem;
|
NSUInteger _transientItem;
|
||||||
|
NSCharacterSet *_siteNameAcceptableCharactersSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Life
|
#pragma mark - Life
|
||||||
@@ -51,12 +52,21 @@
|
|||||||
|
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
NSMutableCharacterSet *siteNameAcceptableCharactersSet = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
|
||||||
|
[siteNameAcceptableCharactersSet formIntersectionWithCharacterSet:[[NSCharacterSet uppercaseLetterCharacterSet] invertedSet]];
|
||||||
|
[siteNameAcceptableCharactersSet addCharactersInString:@"@.-+~&_;:/"];
|
||||||
|
_siteNameAcceptableCharactersSet = siteNameAcceptableCharactersSet;
|
||||||
|
|
||||||
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
||||||
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
||||||
_transientItem = NSNotFound;
|
_transientItem = NSNotFound;
|
||||||
|
|
||||||
self.view.backgroundColor = [UIColor clearColor];
|
self.view.backgroundColor = [UIColor clearColor];
|
||||||
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
|
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
|
||||||
|
self.passwordsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||||
|
if ([self.passwordsSearchBar respondsToSelector:@selector(keyboardAppearance)])
|
||||||
|
self.passwordsSearchBar.keyboardAppearance = UIKeyboardAppearanceDark;
|
||||||
|
else
|
||||||
[self.passwordsSearchBar enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
[self.passwordsSearchBar enumerateViews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
|
||||||
if ([subview isKindOfClass:[UITextField class]])
|
if ([subview isKindOfClass:[UITextField class]])
|
||||||
((UITextField *)subview).keyboardAppearance = UIKeyboardAppearanceDark;
|
((UITextField *)subview).keyboardAppearance = UIKeyboardAppearanceDark;
|
||||||
@@ -68,23 +78,36 @@
|
|||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
[self observeStore];
|
|
||||||
[self updateConfigKey:nil];
|
[self updateConfigKey:nil];
|
||||||
[self updatePasswords];
|
[self updatePasswords];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewDidAppear:animated];
|
||||||
|
|
||||||
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
|
||||||
|
if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context])
|
||||||
|
[PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2];
|
||||||
|
[context saveToStore];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewWillDisappear:animated];
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
[self removeObservers];
|
PearlRemoveNotificationObservers();
|
||||||
[self stopObservingStore];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||||
|
|
||||||
if ([segue.identifier isEqualToString:@"popdown"])
|
if ([segue.identifier isEqualToString:@"popdown"])
|
||||||
_popdownVC = segue.destinationViewController;
|
_popdownVC = segue.destinationViewController;
|
||||||
|
if ([segue.identifier isEqualToString:@"answers"])
|
||||||
|
((MPAnswersViewController *)segue.destinationViewController).site =
|
||||||
|
[[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
|
||||||
@@ -96,12 +119,6 @@
|
|||||||
|
|
||||||
#pragma mark - UICollectionViewDelegateFlowLayout
|
#pragma mark - UICollectionViewDelegateFlowLayout
|
||||||
|
|
||||||
- (CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
|
||||||
referenceSizeForHeaderInSection:(NSInteger)section {
|
|
||||||
|
|
||||||
return CGSizeMake( collectionView.bounds.size.width, CGRectGetBottom( self.passwordsSearchBar.frame ).y );
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
|
||||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
@@ -131,19 +148,13 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
|
|
||||||
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
||||||
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
|
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
|
||||||
[cell setElement:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
|
[cell setSite:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
|
||||||
else
|
else
|
||||||
[cell setTransientSite:self.query animated:NO];
|
[cell setTransientSite:self.query animated:NO];
|
||||||
|
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
|
|
||||||
atIndexPath:(NSIndexPath *)indexPath {
|
|
||||||
|
|
||||||
return [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MPPasswordHeader" forIndexPath:indexPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - UIScrollDelegate
|
#pragma mark - UIScrollDelegate
|
||||||
|
|
||||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||||
@@ -235,12 +246,32 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
|
|
||||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||||
|
|
||||||
if (searchBar == self.passwordsSearchBar)
|
if (searchBar == self.passwordsSearchBar) {
|
||||||
|
if ([self.query length] && [[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
|
||||||
|
[self showTips:MPPasswordsBadNameTip];
|
||||||
|
|
||||||
[self updatePasswords];
|
[self updatePasswords];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
|
|
||||||
|
- (void)showTips:(MPPasswordsTips)showTips {
|
||||||
|
|
||||||
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
|
if (showTips & MPPasswordsBadNameTip)
|
||||||
|
self.badNameTipContainer.alpha = 1;
|
||||||
|
} completion:^(BOOL finished) {
|
||||||
|
if (finished)
|
||||||
|
PearlMainQueueAfter( 5, ^{
|
||||||
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
|
if (showTips & MPPasswordsBadNameTip)
|
||||||
|
self.badNameTipContainer.alpha = 0;
|
||||||
|
}];
|
||||||
|
} );
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)fetchedItemsDidUpdate {
|
- (void)fetchedItemsDidUpdate {
|
||||||
|
|
||||||
NSString *query = self.query;
|
NSString *query = self.query;
|
||||||
@@ -264,91 +295,49 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
|
|
||||||
- (void)registerObservers {
|
- (void)registerObservers {
|
||||||
|
|
||||||
if ([_notificationObservers count])
|
PearlRemoveNotificationObservers();
|
||||||
return;
|
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
Weakify( self );
|
|
||||||
_notificationObservers = @[
|
|
||||||
[[NSNotificationCenter defaultCenter]
|
|
||||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
||||||
Strongify( self );
|
|
||||||
|
|
||||||
self.passwordSelectionContainer.alpha = 0;
|
self.passwordSelectionContainer.alpha = 0;
|
||||||
}],
|
} );
|
||||||
[[NSNotificationCenter defaultCenter]
|
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
|
||||||
addObserverForName:MPSignedOutNotification object:nil
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
[self updatePasswords];
|
||||||
Strongify( self );
|
} );
|
||||||
|
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
|
[UIView animateWithDuration:0.7f animations:^{
|
||||||
|
self.passwordSelectionContainer.alpha = 1;
|
||||||
|
}];
|
||||||
|
} );
|
||||||
|
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
_fetchedResultsController = nil;
|
_fetchedResultsController = nil;
|
||||||
self.passwordsSearchBar.text = nil;
|
self.passwordsSearchBar.text = nil;
|
||||||
[self.passwordCollectionView reloadData];
|
[self.passwordCollectionView reloadData];
|
||||||
}],
|
} );
|
||||||
[[NSNotificationCenter defaultCenter]
|
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue],
|
||||||
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
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 updateConfigKey:note.object];
|
[self updateConfigKey:note.object];
|
||||||
}],
|
} );
|
||||||
];
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
|
||||||
}
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
|
self->_fetchedResultsController = nil;
|
||||||
- (void)removeObservers {
|
[self.passwordCollectionView reloadData];
|
||||||
|
} );
|
||||||
for (id observer in _notificationObservers)
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
_notificationObservers = nil;
|
[self updatePasswords];
|
||||||
}
|
[self registerObservers];
|
||||||
|
} );
|
||||||
- (void)observeStore {
|
|
||||||
|
|
||||||
Weakify( self );
|
|
||||||
|
|
||||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||||
if (!_mocObserver && mainContext)
|
if (mainContext)
|
||||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
|
||||||
addObserverForName:NSManagedObjectContextDidSaveNotification object:mainContext
|
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
if (![[MPiOSAppDelegate get] activeUserInContext:note.object])
|
||||||
if (![[MPiOSAppDelegate get] activeUserInContext:mainContext])
|
|
||||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||||
}];
|
} );
|
||||||
if (!_storeChangingObserver)
|
|
||||||
_storeChangingObserver = [[NSNotificationCenter defaultCenter]
|
|
||||||
addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification object:nil
|
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
|
||||||
Strongify( self );
|
|
||||||
self->_fetchedResultsController = nil;
|
|
||||||
if (self->_mocObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver];
|
|
||||||
[self.passwordCollectionView reloadData];
|
|
||||||
}];
|
|
||||||
if (!_storeChangedObserver)
|
|
||||||
_storeChangedObserver = [[NSNotificationCenter defaultCenter]
|
|
||||||
addObserverForName:NSPersistentStoreCoordinatorStoresDidChangeNotification object:nil
|
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
|
||||||
Strongify( self );
|
|
||||||
[self updatePasswords];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)stopObservingStore {
|
|
||||||
|
|
||||||
if (_mocObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
|
||||||
if (_storeChangingObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
|
|
||||||
if (_storeChangedObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateConfigKey:(NSString *)key {
|
- (void)updateConfigKey:(NSString *)key {
|
||||||
@@ -384,7 +373,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
[NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]:
|
[NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]:
|
||||||
[NSPredicate predicateWithFormat:@"user == %@", activeUserOID];
|
[NSPredicate predicateWithFormat:@"user == %@", activeUserOID];
|
||||||
if (![self.fetchedResultsController performFetch:&error])
|
if (![self.fetchedResultsController performFetch:&error])
|
||||||
err( @"Couldn't fetch elements: %@", error );
|
err( @"Couldn't fetch sites: %@", [error fullDescription] );
|
||||||
|
|
||||||
[self.passwordCollectionView performBatchUpdates:^{
|
[self.passwordCollectionView performBatchUpdates:^{
|
||||||
[self fetchedItemsDidUpdate];
|
[self fetchedItemsDidUpdate];
|
||||||
@@ -396,10 +385,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
|
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
|
||||||
else if (section >= toSections)
|
else if (section >= toSections)
|
||||||
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
|
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
|
||||||
else
|
else if (section < [oldSections count])
|
||||||
[self.passwordCollectionView reloadItemsFromArray:oldSections[section]
|
[self.passwordCollectionView reloadItemsFromArray:oldSections[section]
|
||||||
toArray:[[self.fetchedResultsController sections][section] objects]
|
toArray:[[self.fetchedResultsController sections][section] objects]
|
||||||
inSection:section];
|
inSection:section];
|
||||||
|
else
|
||||||
|
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
|
||||||
}
|
}
|
||||||
} completion:^(BOOL finished) {
|
} completion:^(BOOL finished) {
|
||||||
if (finished)
|
if (finished)
|
||||||
@@ -421,7 +412,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
if (!_fetchedResultsController) {
|
if (!_fetchedResultsController) {
|
||||||
_showTransientItem = NO;
|
_showTransientItem = NO;
|
||||||
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
||||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||||
fetchRequest.sortDescriptors = @[
|
fetchRequest.sortDescriptors = @[
|
||||||
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
|
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
|
||||||
];
|
];
|
||||||
@@ -430,7 +421,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
|
|||||||
initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
|
initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
|
||||||
_fetchedResultsController.delegate = self;
|
_fetchedResultsController.delegate = self;
|
||||||
}];
|
}];
|
||||||
[self observeStore];
|
[self registerObservers];
|
||||||
}
|
}
|
||||||
|
|
||||||
return _fetchedResultsController;
|
return _fetchedResultsController;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
@implementation MPPopdownSegue {
|
@implementation MPPopdownSegue {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char UnwindingObserverKey;
|
||||||
|
|
||||||
- (void)perform {
|
- (void)perform {
|
||||||
|
|
||||||
MPPasswordsViewController *passwordsVC;
|
MPPasswordsViewController *passwordsVC;
|
||||||
@@ -41,11 +43,24 @@
|
|||||||
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
|
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
|
||||||
} completion:^(BOOL finished) {
|
} completion:^(BOOL finished) {
|
||||||
[popdownVC didMoveToParentViewController:passwordsVC];
|
[popdownVC didMoveToParentViewController:passwordsVC];
|
||||||
|
|
||||||
|
id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
|
||||||
|
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||||
|
^(NSNotification *note) {
|
||||||
|
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC
|
||||||
|
destination:passwordsVC] perform];
|
||||||
|
}];
|
||||||
|
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, observer, OBJC_ASSOCIATION_RETAIN );
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
else if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
|
else {
|
||||||
popdownVC = self.sourceViewController;
|
popdownVC = self.sourceViewController;
|
||||||
passwordsVC = self.destinationViewController;
|
for (passwordsVC = self.sourceViewController; passwordsVC && ![(id)passwordsVC isKindOfClass:[MPPasswordsViewController class]];
|
||||||
|
passwordsVC = (id)passwordsVC.parentViewController);
|
||||||
|
NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." );
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:objc_getAssociatedObject( popdownVC, &UnwindingObserverKey )];
|
||||||
|
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, nil, OBJC_ASSOCIATION_RETAIN );
|
||||||
|
|
||||||
[popdownVC willMoveToParentViewController:nil];
|
[popdownVC willMoveToParentViewController:nil];
|
||||||
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
|
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
inf( @"Preferences will appear" );
|
inf( @"Preferences will appear" );
|
||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.passwordsPreferences"];
|
||||||
|
|
||||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
|
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
|
||||||
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
|
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
self.generatedTypeControl.selectedSegmentIndex = -1;
|
self.generatedTypeControl.selectedSegmentIndex = -1;
|
||||||
|
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPElementType defaultType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = [self typeForSelectedSegment];
|
MPSiteType defaultType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = [self typeForSelectedSegment];
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
@@ -179,31 +180,31 @@
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (enum MPElementType)typeForSelectedSegment {
|
- (enum MPSiteType)typeForSelectedSegment {
|
||||||
|
|
||||||
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex;
|
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex;
|
||||||
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
|
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
|
||||||
|
|
||||||
switch (selectedGeneratedIndex) {
|
switch (selectedGeneratedIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return MPElementTypeGeneratedMaximum;
|
return MPSiteTypeGeneratedMaximum;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
case 2:
|
case 2:
|
||||||
return MPElementTypeGeneratedMedium;
|
return MPSiteTypeGeneratedMedium;
|
||||||
case 3:
|
case 3:
|
||||||
return MPElementTypeGeneratedBasic;
|
return MPSiteTypeGeneratedBasic;
|
||||||
case 4:
|
case 4:
|
||||||
return MPElementTypeGeneratedShort;
|
return MPSiteTypeGeneratedShort;
|
||||||
case 5:
|
case 5:
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPSiteTypeGeneratedPIN;
|
||||||
default:
|
default:
|
||||||
|
|
||||||
switch (selectedStoredIndex) {
|
switch (selectedStoredIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return MPElementTypeStoredPersonal;
|
return MPSiteTypeStoredPersonal;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeStoredDevicePrivate;
|
return MPSiteTypeStoredDevicePrivate;
|
||||||
default:
|
default:
|
||||||
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex,
|
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex,
|
||||||
(long)selectedStoredIndex );
|
(long)selectedStoredIndex );
|
||||||
@@ -211,32 +212,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)generatedSegmentIndexForType:(MPElementType)type {
|
- (NSInteger)generatedSegmentIndexForType:(MPSiteType)type {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedMaximum:
|
case MPSiteTypeGeneratedMaximum:
|
||||||
return 0;
|
return 0;
|
||||||
case MPElementTypeGeneratedLong:
|
case MPSiteTypeGeneratedLong:
|
||||||
return 1;
|
return 1;
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPSiteTypeGeneratedMedium:
|
||||||
return 2;
|
return 2;
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPSiteTypeGeneratedBasic:
|
||||||
return 3;
|
return 3;
|
||||||
case MPElementTypeGeneratedShort:
|
case MPSiteTypeGeneratedShort:
|
||||||
return 4;
|
return 4;
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPSiteTypeGeneratedPIN:
|
||||||
return 5;
|
return 5;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)storedSegmentIndexForType:(MPElementType)type {
|
- (NSInteger)storedSegmentIndexForType:(MPSiteType)type {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeStoredPersonal:
|
case MPSiteTypeStoredPersonal:
|
||||||
return 0;
|
return 0;
|
||||||
case MPElementTypeStoredDevicePrivate:
|
case MPSiteTypeStoredDevicePrivate:
|
||||||
return 1;
|
return 1;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -9,16 +9,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// MPEmergencySegue.h
|
// MPRootSegue.h
|
||||||
// MPEmergencySegue
|
// MPRootSegue
|
||||||
//
|
//
|
||||||
// Created by lhunath on 2014-04-09.
|
// Created by lhunath on 2014-09-26.
|
||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@interface MPEmergencySegue : UIStoryboardSegue
|
|
||||||
|
|
||||||
@property(nonatomic) BOOL unwind;
|
@interface MPRootSegue : UIStoryboardSegue
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
38
MasterPassword/ObjC/iOS/MPRootSegue.m
Normal file
38
MasterPassword/ObjC/iOS/MPRootSegue.m
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// MPRootSegue.h
|
||||||
|
// MPRootSegue
|
||||||
|
//
|
||||||
|
// Created by lhunath on 2014-09-26.
|
||||||
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPRootSegue.h"
|
||||||
|
|
||||||
|
|
||||||
|
@implementation MPRootSegue {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)perform {
|
||||||
|
|
||||||
|
UIViewController *sourceViewController = self.sourceViewController;
|
||||||
|
UIViewController *destinationViewController = self.destinationViewController;
|
||||||
|
[sourceViewController addChildViewController:destinationViewController];
|
||||||
|
destinationViewController.view.frame = sourceViewController.view.bounds;
|
||||||
|
destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
|
[sourceViewController.view addSubview:destinationViewController.view];
|
||||||
|
[destinationViewController didMoveToParentViewController:sourceViewController];
|
||||||
|
[sourceViewController setNeedsStatusBarAppearanceUpdate];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
@interface MPSetupViewController : UIViewController
|
@interface MPSetupViewController : UIViewController
|
||||||
|
|
||||||
@property(weak, nonatomic) IBOutlet UISwitch *rememberLoginSwitch;
|
@property(weak, nonatomic) IBOutlet UISwitch *rememberLoginSwitch;
|
||||||
|
@property(weak, nonatomic) IBOutlet UISwitch *showPasswordsSwitch;
|
||||||
|
|
||||||
- (IBAction)close:(UIBarButtonItem *)sender;
|
- (IBAction)close:(UIBarButtonItem *)sender;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
if (self.rememberLoginSwitch)
|
if (self.rememberLoginSwitch)
|
||||||
self.rememberLoginSwitch.on = [[MPiOSConfig get].rememberLogin boolValue];
|
self.rememberLoginSwitch.on = [[MPiOSConfig get].rememberLogin boolValue];
|
||||||
|
if (self.showPasswordsSwitch)
|
||||||
|
self.showPasswordsSwitch.on = ![[MPiOSConfig get].hidePasswords boolValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
@@ -36,6 +38,8 @@
|
|||||||
|
|
||||||
if (self.rememberLoginSwitch)
|
if (self.rememberLoginSwitch)
|
||||||
[MPiOSConfig get].rememberLogin = @(self.rememberLoginSwitch.on);
|
[MPiOSConfig get].rememberLogin = @(self.rememberLoginSwitch.on);
|
||||||
|
if (self.showPasswordsSwitch)
|
||||||
|
[MPiOSConfig get].hidePasswords = @(!self.showPasswordsSwitch.on);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)close:(UIBarButtonItem *)sender {
|
- (IBAction)close:(UIBarButtonItem *)sender {
|
||||||
|
|||||||
35
MasterPassword/ObjC/iOS/MPStoreViewController.h
Normal file
35
MasterPassword/ObjC/iOS/MPStoreViewController.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// MPPreferencesViewController.h
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 04/06/12.
|
||||||
|
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@class MPStoreProductCell;
|
||||||
|
|
||||||
|
@interface MPStoreViewController : PearlMutableStaticTableViewController
|
||||||
|
|
||||||
|
@property(weak, nonatomic) IBOutlet MPStoreProductCell *generateLoginCell;
|
||||||
|
@property(weak, nonatomic) IBOutlet MPStoreProductCell *generateAnswersCell;
|
||||||
|
@property(weak, nonatomic) IBOutlet MPStoreProductCell *iOSIntegrationCell;
|
||||||
|
@property(weak, nonatomic) IBOutlet MPStoreProductCell *touchIDCell;
|
||||||
|
@property(weak, nonatomic) IBOutlet MPStoreProductCell *fuelCell;
|
||||||
|
@property(weak, nonatomic) IBOutlet UITableViewCell *loadingCell;
|
||||||
|
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *fuelMeterConstraint;
|
||||||
|
@property(weak, nonatomic) IBOutlet UIButton *fuelSpeedButton;
|
||||||
|
@property(weak, nonatomic) IBOutlet UILabel *fuelStatusLabel;
|
||||||
|
|
||||||
|
+ (NSString *)latestStoreFeatures;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface MPStoreProductCell : UITableViewCell
|
||||||
|
|
||||||
|
@property(nonatomic) IBOutlet UILabel *priceLabel;
|
||||||
|
@property(nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
|
||||||
|
@property(nonatomic) IBOutlet UIView *purchasedIndicator;
|
||||||
|
|
||||||
|
@end
|
||||||
331
MasterPassword/ObjC/iOS/MPStoreViewController.m
Normal file
331
MasterPassword/ObjC/iOS/MPStoreViewController.m
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
//
|
||||||
|
// MPPreferencesViewController.m
|
||||||
|
// MasterPassword-iOS
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 04/06/12.
|
||||||
|
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "MPStoreViewController.h"
|
||||||
|
#import "MPiOSAppDelegate.h"
|
||||||
|
#import "UIColor+Expanded.h"
|
||||||
|
#import "MPAppDelegate_InApp.h"
|
||||||
|
#import "MPPasswordsViewController.h"
|
||||||
|
|
||||||
|
PearlEnum( MPDevelopmentFuelConsumption,
|
||||||
|
MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly );
|
||||||
|
|
||||||
|
@interface MPStoreViewController()<MPInAppDelegate>
|
||||||
|
|
||||||
|
@property(nonatomic, strong) NSNumberFormatter *currencyFormatter;
|
||||||
|
@property(nonatomic, strong) NSArray *products;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPStoreViewController
|
||||||
|
|
||||||
|
+ (NSString *)latestStoreFeatures {
|
||||||
|
|
||||||
|
NSMutableString *features = [NSMutableString string];
|
||||||
|
NSArray *storeVersions = @[
|
||||||
|
@"Generated Usernames\nSecurity Question Answers"
|
||||||
|
];
|
||||||
|
NSInteger storeVersion = [[NSUserDefaults standardUserDefaults] integerForKey:@"storeVersion"];
|
||||||
|
for (; storeVersion < [storeVersions count]; ++storeVersion)
|
||||||
|
[features appendFormat:@"%@\n", storeVersions[storeVersion]];
|
||||||
|
if (![features length])
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
[[NSUserDefaults standardUserDefaults] setInteger:storeVersion forKey:@"storeVersion"];
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.currencyFormatter = [NSNumberFormatter new];
|
||||||
|
self.currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
|
||||||
|
|
||||||
|
self.tableView.tableHeaderView = [UIView new];
|
||||||
|
self.tableView.tableFooterView = [UIView new];
|
||||||
|
self.view.backgroundColor = [UIColor clearColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillAppear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
|
||||||
|
|
||||||
|
[self reloadCellsHiding:self.allCellsBySection[0] showing:@[ self.loadingCell ]];
|
||||||
|
[self.allCellsBySection[0] enumerateObjectsUsingBlock:^(MPStoreProductCell *cell, NSUInteger idx, BOOL *stop) {
|
||||||
|
if ([cell isKindOfClass:[MPStoreProductCell class]]) {
|
||||||
|
cell.purchasedIndicator.alpha = 0;
|
||||||
|
[cell.activityIndicator stopAnimating];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPStoreViewController *self, NSNotification *note) {
|
||||||
|
[self updateProducts];
|
||||||
|
[self updateFuel];
|
||||||
|
} );
|
||||||
|
[[MPiOSAppDelegate get] registerProductsObserver:self];
|
||||||
|
[self updateFuel];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
|
PearlRemoveNotificationObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDataSource
|
||||||
|
|
||||||
|
- (MPStoreProductCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
MPStoreProductCell *cell = (MPStoreProductCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
if (cell.contentView.translatesAutoresizingMaskIntoConstraints) {
|
||||||
|
cell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
[cell addConstraints:@[
|
||||||
|
[NSLayoutConstraint constraintWithItem:cell attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
|
||||||
|
toItem:cell.contentView attribute:NSLayoutAttributeTop multiplier:1 constant:0],
|
||||||
|
[NSLayoutConstraint constraintWithItem:cell attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
|
||||||
|
toItem:cell.contentView attribute:NSLayoutAttributeRight multiplier:1 constant:0],
|
||||||
|
[NSLayoutConstraint constraintWithItem:cell attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
|
||||||
|
toItem:cell.contentView attribute:NSLayoutAttributeLeft multiplier:1 constant:0],
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexPath.section == 0)
|
||||||
|
cell.selectionStyle = [[MPiOSAppDelegate get] isFeatureUnlocked:[self productForCell:cell].productIdentifier]?
|
||||||
|
UITableViewCellSelectionStyleNone: UITableViewCellSelectionStyleDefault;
|
||||||
|
|
||||||
|
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
|
||||||
|
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
|
||||||
|
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDelegate
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
[cell layoutIfNeeded];
|
||||||
|
[cell layoutIfNeeded];
|
||||||
|
|
||||||
|
dbg_return_tr( cell.contentView.bounds.size.height, @, indexPath );
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
MPStoreProductCell *cell = (MPStoreProductCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
if (cell.selectionStyle == UITableViewCellSelectionStyleNone)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SKProduct *product = [self productForCell:cell];
|
||||||
|
if (product && ![[MPAppDelegate_Shared get] isFeatureUnlocked:product.productIdentifier])
|
||||||
|
[[MPAppDelegate_Shared get] purchaseProductWithIdentifier:product.productIdentifier
|
||||||
|
quantity:[self quantityForProductIdentifier:product.productIdentifier]];
|
||||||
|
|
||||||
|
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
- (IBAction)toggleFuelConsumption:(id)sender {
|
||||||
|
|
||||||
|
NSUInteger fuelConsumption = [[MPiOSConfig get].developmentFuelConsumption unsignedIntegerValue];
|
||||||
|
[MPiOSConfig get].developmentFuelConsumption = @((fuelConsumption + 1) % MPDevelopmentFuelConsumptionCount);
|
||||||
|
[self updateProducts];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)restorePurchases:(id)sender {
|
||||||
|
|
||||||
|
[PearlAlert showAlertWithTitle:@"Restore Previous Purchases" message:
|
||||||
|
@"This will check with Apple to find and activate any purchases you made from other devices."
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||||
|
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == [alert cancelButtonIndex])
|
||||||
|
return;
|
||||||
|
|
||||||
|
[[MPAppDelegate_Shared get] restoreCompletedTransactions];
|
||||||
|
} cancelTitle:@"Cancel" otherTitles:@"Find Purchases", nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)sendThanks:(id)sender {
|
||||||
|
|
||||||
|
[[self dismissPopup].navigationController performSegueWithIdentifier:@"web" sender:
|
||||||
|
[NSURL URLWithString:@"http://thanks.lhunath.com"]];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - MPInAppDelegate
|
||||||
|
|
||||||
|
- (void)updateWithProducts:(NSArray *)products {
|
||||||
|
|
||||||
|
self.products = products;
|
||||||
|
|
||||||
|
[self updateProducts];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction {
|
||||||
|
|
||||||
|
MPStoreProductCell *cell = [self cellForProductIdentifier:transaction.payment.productIdentifier];
|
||||||
|
if (!cell)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (transaction.transactionState) {
|
||||||
|
case SKPaymentTransactionStatePurchasing:
|
||||||
|
[cell.activityIndicator startAnimating];
|
||||||
|
break;
|
||||||
|
case SKPaymentTransactionStatePurchased:
|
||||||
|
[cell.activityIndicator stopAnimating];
|
||||||
|
break;
|
||||||
|
case SKPaymentTransactionStateFailed:
|
||||||
|
[cell.activityIndicator stopAnimating];
|
||||||
|
break;
|
||||||
|
case SKPaymentTransactionStateRestored:
|
||||||
|
[cell.activityIndicator stopAnimating];
|
||||||
|
break;
|
||||||
|
case SKPaymentTransactionStateDeferred:
|
||||||
|
[cell.activityIndicator startAnimating];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (SKProduct *)productForCell:(MPStoreProductCell *)cell {
|
||||||
|
|
||||||
|
for (SKProduct *product in self.products)
|
||||||
|
if ([self cellForProductIdentifier:product.productIdentifier] == cell)
|
||||||
|
return product;
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MPStoreProductCell *)cellForProductIdentifier:(NSString *)productIdentifier {
|
||||||
|
|
||||||
|
if ([productIdentifier isEqualToString:MPProductGenerateLogins])
|
||||||
|
return self.generateLoginCell;
|
||||||
|
if ([productIdentifier isEqualToString:MPProductGenerateAnswers])
|
||||||
|
return self.generateAnswersCell;
|
||||||
|
if ([productIdentifier isEqualToString:MPProductOSIntegration])
|
||||||
|
return self.iOSIntegrationCell;
|
||||||
|
if ([productIdentifier isEqualToString:MPProductTouchID])
|
||||||
|
return self.touchIDCell;
|
||||||
|
if ([productIdentifier isEqualToString:MPProductFuel])
|
||||||
|
return self.fuelCell;
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateProducts {
|
||||||
|
|
||||||
|
NSMutableArray *showCells = [NSMutableArray array];
|
||||||
|
NSMutableArray *hideCells = [NSMutableArray array];
|
||||||
|
[hideCells addObjectsFromArray:self.allCellsBySection[0]];
|
||||||
|
[hideCells addObject:self.loadingCell];
|
||||||
|
|
||||||
|
for (SKProduct *product in self.products) {
|
||||||
|
[self showCellForProductWithIdentifier:MPProductGenerateLogins ifProduct:product showingCells:showCells];
|
||||||
|
[self showCellForProductWithIdentifier:MPProductGenerateAnswers ifProduct:product showingCells:showCells];
|
||||||
|
[self showCellForProductWithIdentifier:MPProductOSIntegration ifProduct:product showingCells:showCells];
|
||||||
|
[self showCellForProductWithIdentifier:MPProductTouchID ifProduct:product showingCells:showCells];
|
||||||
|
[self showCellForProductWithIdentifier:MPProductFuel ifProduct:product showingCells:showCells];
|
||||||
|
}
|
||||||
|
|
||||||
|
[hideCells removeObjectsInArray:showCells];
|
||||||
|
[self reloadCellsHiding:hideCells showing:showCells];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateFuel {
|
||||||
|
|
||||||
|
CGFloat weeklyFuelConsumption = [self weeklyFuelConsumption]; /* consume x fuel / week */
|
||||||
|
CGFloat fuelRemaining = [[MPiOSConfig get].developmentFuelRemaining floatValue]; /* x fuel left */
|
||||||
|
CGFloat fuelInvested = [[MPiOSConfig get].developmentFuelInvested floatValue]; /* x fuel left */
|
||||||
|
NSDate *now = [NSDate date];
|
||||||
|
NSTimeInterval fuelSecondsElapsed = -[[MPiOSConfig get].developmentFuelChecked timeIntervalSinceDate:now];
|
||||||
|
if (fuelSecondsElapsed > 3600 || ![MPiOSConfig get].developmentFuelChecked) {
|
||||||
|
NSTimeInterval weeksElapsed = fuelSecondsElapsed / (3600 * 24 * 7 /* 1 week */); /* x weeks elapsed */
|
||||||
|
NSTimeInterval fuelConsumed = weeklyFuelConsumption * weeksElapsed;
|
||||||
|
fuelRemaining -= fuelConsumed;
|
||||||
|
fuelInvested += fuelConsumed;
|
||||||
|
[MPiOSConfig get].developmentFuelChecked = now;
|
||||||
|
[MPiOSConfig get].developmentFuelRemaining = @(fuelRemaining);
|
||||||
|
[MPiOSConfig get].developmentFuelInvested = @(fuelInvested);
|
||||||
|
}
|
||||||
|
|
||||||
|
CGFloat fuelRatio = weeklyFuelConsumption == 0? 0: fuelRemaining / weeklyFuelConsumption; /* x weeks worth of fuel left */
|
||||||
|
[self.fuelMeterConstraint updateConstant:MIN( 0.5f, fuelRatio - 0.5f ) * 160]; /* -80pt = 0 weeks left, 80pt = >=1 week left */
|
||||||
|
self.fuelStatusLabel.text = strf( @"fuel left: %0.1f work hours\ninvested: %0.1f work hours", fuelRemaining, fuelInvested );
|
||||||
|
self.fuelStatusLabel.hidden = (fuelRemaining + fuelInvested) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)weeklyFuelConsumption {
|
||||||
|
|
||||||
|
switch ((MPDevelopmentFuelConsumption)[[MPiOSConfig get].developmentFuelConsumption unsignedIntegerValue]) {
|
||||||
|
case MPDevelopmentFuelConsumptionQuarterly:
|
||||||
|
[self.fuelSpeedButton setTitle:@"1h / quarter" forState:UIControlStateNormal];
|
||||||
|
return 1.f / 12 /* 12 weeks */;
|
||||||
|
case MPDevelopmentFuelConsumptionMonthly:
|
||||||
|
[self.fuelSpeedButton setTitle:@"1h / month" forState:UIControlStateNormal];
|
||||||
|
return 1.f / 4 /* 4 weeks */;
|
||||||
|
case MPDevelopmentFuelWeekly:
|
||||||
|
[self.fuelSpeedButton setTitle:@"1h / week" forState:UIControlStateNormal];
|
||||||
|
return 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showCellForProductWithIdentifier:(NSString *)productIdentifier ifProduct:(SKProduct *)product
|
||||||
|
showingCells:(NSMutableArray *)showCells {
|
||||||
|
|
||||||
|
if (![product.productIdentifier isEqualToString:productIdentifier])
|
||||||
|
return;
|
||||||
|
|
||||||
|
MPStoreProductCell *cell = [self cellForProductIdentifier:productIdentifier];
|
||||||
|
[showCells addObject:cell];
|
||||||
|
|
||||||
|
self.currencyFormatter.locale = product.priceLocale;
|
||||||
|
BOOL purchased = [[MPiOSAppDelegate get] isFeatureUnlocked:productIdentifier];
|
||||||
|
NSInteger quantity = [self quantityForProductIdentifier:productIdentifier];
|
||||||
|
cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:@([product.price floatValue] * quantity)];
|
||||||
|
cell.purchasedIndicator.alpha = purchased? 1: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)quantityForProductIdentifier:(NSString *)productIdentifier {
|
||||||
|
|
||||||
|
if ([productIdentifier isEqualToString:MPProductFuel])
|
||||||
|
return (NSInteger)(MP_FUEL_HOURLY_RATE * [self weeklyFuelConsumption] + .5f);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPStoreProductCell
|
||||||
|
@end
|
||||||
@@ -13,11 +13,11 @@
|
|||||||
@protocol MPTypeDelegate<NSObject>
|
@protocol MPTypeDelegate<NSObject>
|
||||||
|
|
||||||
@required
|
@required
|
||||||
- (void)didSelectType:(MPElementType)type;
|
- (void)didSelectType:(MPSiteType)type;
|
||||||
- (MPElementType)selectedType;
|
- (MPSiteType)selectedType;
|
||||||
|
|
||||||
@optional
|
@optional
|
||||||
- (MPElementEntity *)selectedElement;
|
- (MPSiteEntity *)selectedSite;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
@interface MPTypeViewController()
|
@interface MPTypeViewController()
|
||||||
|
|
||||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
|
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -63,21 +63,21 @@
|
|||||||
|
|
||||||
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
|
||||||
MPElementEntity *selectedElement = nil;
|
MPSiteEntity *selectedSite = nil;
|
||||||
if ([self.delegate respondsToSelector:@selector(selectedElement)])
|
if ([self.delegate respondsToSelector:@selector( selectedSite )])
|
||||||
selectedElement = [self.delegate selectedElement];
|
selectedSite = [self.delegate selectedSite];
|
||||||
|
|
||||||
MPElementType cellType = [self typeAtIndexPath:indexPath];
|
MPSiteType cellType = [self typeAtIndexPath:indexPath];
|
||||||
MPElementType selectedType = selectedElement? selectedElement.type: [self.delegate selectedType];
|
MPSiteType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
|
||||||
cell.selected = (selectedType == cellType);
|
cell.selected = (selectedType == cellType);
|
||||||
|
|
||||||
if (cellType != (MPElementType)NSNotFound && cellType & MPElementTypeClassGenerated) {
|
if (cellType != (MPSiteType)NSNotFound && cellType & MPSiteTypeClassGenerated) {
|
||||||
[(UITextField *)[cell viewWithTag:2] setText:@"..."];
|
[(UITextField *)[cell viewWithTag:2] setText:@"..."];
|
||||||
|
|
||||||
NSString *name = selectedElement.name;
|
NSString *name = selectedSite.name;
|
||||||
NSUInteger counter = 0;
|
NSUInteger counter = 0;
|
||||||
if ([selectedElement isKindOfClass:[MPElementGeneratedEntity class]])
|
if ([selectedSite isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||||
counter = ((MPElementGeneratedEntity *)selectedElement).counter;
|
counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
|
||||||
|
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
|
||||||
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
|
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
|
||||||
@@ -96,8 +96,8 @@
|
|||||||
|
|
||||||
NSAssert(self.navigationController.topViewController == self, @"Not the currently active navigation item.");
|
NSAssert(self.navigationController.topViewController == self, @"Not the currently active navigation item.");
|
||||||
|
|
||||||
MPElementType type = [self typeAtIndexPath:indexPath];
|
MPSiteType type = [self typeAtIndexPath:indexPath];
|
||||||
if (type == (MPElementType)NSNotFound)
|
if (type == (MPSiteType)NSNotFound)
|
||||||
// Selected a non-type row.
|
// Selected a non-type row.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -105,31 +105,31 @@
|
|||||||
[self.navigationController popViewControllerAnimated:YES];
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
|
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
switch (indexPath.section) {
|
switch (indexPath.section) {
|
||||||
case 0: {
|
case 0: {
|
||||||
// Generated
|
// Generated
|
||||||
switch (indexPath.row) {
|
switch (indexPath.row) {
|
||||||
case 0:
|
case 0:
|
||||||
return (MPElementType)NSNotFound;
|
return (MPSiteType)NSNotFound;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeGeneratedMaximum;
|
return MPSiteTypeGeneratedMaximum;
|
||||||
case 2:
|
case 2:
|
||||||
return MPElementTypeGeneratedLong;
|
return MPSiteTypeGeneratedLong;
|
||||||
case 3:
|
case 3:
|
||||||
return MPElementTypeGeneratedMedium;
|
return MPSiteTypeGeneratedMedium;
|
||||||
case 4:
|
case 4:
|
||||||
return MPElementTypeGeneratedBasic;
|
return MPSiteTypeGeneratedBasic;
|
||||||
case 5:
|
case 5:
|
||||||
return MPElementTypeGeneratedShort;
|
return MPSiteTypeGeneratedShort;
|
||||||
case 6:
|
case 6:
|
||||||
return MPElementTypeGeneratedPIN;
|
return MPSiteTypeGeneratedPIN;
|
||||||
case 7:
|
case 7:
|
||||||
return (MPElementType)NSNotFound;
|
return (MPSiteType)NSNotFound;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
Throw(@"Unsupported row: %ld, when selecting generated element type.", (long)indexPath.row);
|
Throw(@"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,22 +138,22 @@
|
|||||||
// Stored
|
// Stored
|
||||||
switch (indexPath.row) {
|
switch (indexPath.row) {
|
||||||
case 0:
|
case 0:
|
||||||
return (MPElementType)NSNotFound;
|
return (MPSiteType)NSNotFound;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeStoredPersonal;
|
return MPSiteTypeStoredPersonal;
|
||||||
case 2:
|
case 2:
|
||||||
return MPElementTypeStoredDevicePrivate;
|
return MPSiteTypeStoredDevicePrivate;
|
||||||
case 3:
|
case 3:
|
||||||
return (MPElementType)NSNotFound;
|
return (MPSiteType)NSNotFound;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
Throw(@"Unsupported row: %ld, when selecting stored element type.", (long)indexPath.row);
|
Throw(@"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Throw(@"Unsupported section: %ld, when selecting element type.", (long)indexPath.section);
|
Throw(@"Unsupported section: %ld, when selecting site type.", (long)indexPath.section);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,25 +16,27 @@
|
|||||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
@interface MPUsersViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITextFieldDelegate>
|
@interface MPUsersViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITextFieldDelegate>
|
||||||
|
|
||||||
@property(weak, nonatomic) IBOutlet UIView *userSelectionContainer;
|
@property(weak, nonatomic) IBOutlet UIView *userSelectionContainer;
|
||||||
@property(weak, nonatomic) IBOutlet UIButton *marqueeButton;
|
@property(weak, nonatomic) IBOutlet UIButton *marqueeButton;
|
||||||
@property(weak, nonatomic) IBOutlet UIView *gitTipTip;
|
|
||||||
@property(weak, nonatomic) IBOutlet UITextField *entryField;
|
@property(weak, nonatomic) IBOutlet UITextField *entryField;
|
||||||
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
|
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
|
||||||
@property(weak, nonatomic) IBOutlet UILabel *entryTipTitleLabel;
|
@property(weak, nonatomic) IBOutlet UILabel *entryTipTitleLabel;
|
||||||
@property(weak, nonatomic) IBOutlet UILabel *entryTipSubtitleLabel;
|
@property(weak, nonatomic) IBOutlet UILabel *entryTipSubtitleLabel;
|
||||||
@property(weak, nonatomic) IBOutlet UIView *entryTipContainer;
|
|
||||||
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
|
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
|
||||||
@property(weak, nonatomic) IBOutlet UIView *footerContainer;
|
@property(weak, nonatomic) IBOutlet UIView *footerContainer;
|
||||||
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *storeLoadingActivity;
|
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *storeLoadingActivity;
|
||||||
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
|
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
|
||||||
@property (strong, nonatomic) IBOutlet UIButton *nextAvatarButton;
|
@property(weak, nonatomic) IBOutlet UIView *avatarTipContainer;
|
||||||
@property (strong, nonatomic) IBOutlet UIButton *previousAvatarButton;
|
@property(weak, nonatomic) IBOutlet UIView *entryTipContainer;
|
||||||
|
@property(weak, nonatomic) IBOutlet UIView *preferencesTipContainer;
|
||||||
|
@property(weak, nonatomic) IBOutlet UIView *thanksTipContainer;
|
||||||
|
@property(weak, nonatomic) IBOutlet UIButton *nextAvatarButton;
|
||||||
|
@property(weak, nonatomic) IBOutlet UIButton *previousAvatarButton;
|
||||||
|
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *keyboardHeightConstraint;
|
||||||
|
|
||||||
@property(assign, nonatomic) BOOL active;
|
@property(assign, nonatomic, readonly) BOOL active;
|
||||||
|
|
||||||
- (void)setActive:(BOOL)active animated:(BOOL)animated;
|
- (void)setActive:(BOOL)active animated:(BOOL)animated;
|
||||||
- (IBAction)changeAvatar:(UIButton *)sender;
|
- (IBAction)changeAvatar:(UIButton *)sender;
|
||||||
|
|||||||
@@ -22,9 +22,14 @@
|
|||||||
#import "MPiOSAppDelegate.h"
|
#import "MPiOSAppDelegate.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
#import "PearlSizedTextView.h"
|
|
||||||
#import "MPWebViewController.h"
|
#import "MPWebViewController.h"
|
||||||
#import "UIView+FontScale.h"
|
|
||||||
|
typedef NS_OPTIONS( NSUInteger, MPUsersTips ) {
|
||||||
|
MPUsersThanksTip = 1 << 0,
|
||||||
|
MPUsersAvatarTip = 1 << 1,
|
||||||
|
MPUsersMasterPasswordTip = 1 << 2,
|
||||||
|
MPUsersPreferencesTip = 1 << 3,
|
||||||
|
};
|
||||||
|
|
||||||
typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
||||||
/** The users are all inactive */
|
/** The users are all inactive */
|
||||||
@@ -51,10 +56,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPUsersViewController {
|
@implementation MPUsersViewController {
|
||||||
__weak id _storeChangingObserver;
|
|
||||||
__weak id _storeChangedObserver;
|
|
||||||
__weak id _mocObserver;
|
|
||||||
NSArray *_notificationObservers;
|
|
||||||
NSString *_masterPasswordChoice;
|
NSString *_masterPasswordChoice;
|
||||||
NSOperationQueue *_afterUpdates;
|
NSOperationQueue *_afterUpdates;
|
||||||
}
|
}
|
||||||
@@ -74,6 +75,12 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
self.view.backgroundColor = [UIColor clearColor];
|
self.view.backgroundColor = [UIColor clearColor];
|
||||||
self.avatarCollectionView.allowsMultipleSelection = YES;
|
self.avatarCollectionView.allowsMultipleSelection = YES;
|
||||||
[self.entryField addTarget:self action:@selector( textFieldEditingChanged: ) forControlEvents:UIControlEventEditingChanged];
|
[self.entryField addTarget:self action:@selector( textFieldEditingChanged: ) forControlEvents:UIControlEventEditingChanged];
|
||||||
|
|
||||||
|
self.preferencesTipContainer.alpha = 0;
|
||||||
|
|
||||||
|
[self setActive:YES animated:NO];
|
||||||
|
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"tipped.thanks"])
|
||||||
|
[self showTips:MPUsersThanksTip];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated {
|
- (void)viewWillAppear:(BOOL)animated {
|
||||||
@@ -81,15 +88,13 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
self.userSelectionContainer.alpha = 0;
|
self.userSelectionContainer.alpha = 0;
|
||||||
[self setActive:YES animated:NO];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewWillDisappear:animated];
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
[self removeObservers];
|
PearlRemoveNotificationObservers();
|
||||||
[self stopObservingStore];
|
|
||||||
|
|
||||||
[self.marqueeTipTimer invalidate];
|
[self.marqueeTipTimer invalidate];
|
||||||
}
|
}
|
||||||
@@ -98,7 +103,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
|||||||
|
|
||||||
[super viewDidAppear:animated];
|
[super viewDidAppear:animated];
|
||||||
|
|
||||||
[self observeStore];
|
|
||||||
[self registerObservers];
|
[self registerObservers];
|
||||||
[self reloadUsers];
|
[self reloadUsers];
|
||||||
|
|
||||||
@@ -398,33 +402,22 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
|
|
||||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||||
// Delete User
|
// Delete User
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[PearlAlert showParentalGateWithTitle:@"Deleting User" message:
|
||||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
@"The user and its sites will be deleted.\nPlease confirm by solving:"
|
||||||
if (!user_)
|
completion:^(BOOL continuing) {
|
||||||
return;
|
if (continuing)
|
||||||
|
[self deleteUser:userID];
|
||||||
[context deleteObject:user_];
|
|
||||||
[context saveToStore];
|
|
||||||
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
|
|
||||||
}];
|
}];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttonIndex == [sheet firstOtherButtonIndex])
|
if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||||
// Reset Password
|
// Reset Password
|
||||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[PearlAlert showParentalGateWithTitle:@"Resetting User" message:
|
||||||
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
@"The user's master password will be reset.\nPlease confirm by solving:"
|
||||||
if (!user_)
|
completion:^(BOOL continuing) {
|
||||||
return;
|
if (continuing)
|
||||||
|
[self resetUser:userID avatar:avatarCell];
|
||||||
[[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
|
} cancelTitle:[PearlStrings get].commonButtonCancel
|
||||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||||
@@ -448,6 +441,66 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
|
|
||||||
|
- (void)deleteUser:(NSManagedObjectID *)userID {
|
||||||
|
|
||||||
|
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
|
MPUserEntity
|
||||||
|
*user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
|
||||||
|
if (!user_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
[context deleteObject:user_];
|
||||||
|
[context saveToStore];
|
||||||
|
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell {
|
||||||
|
|
||||||
|
[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];
|
||||||
|
} );
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showTips:(MPUsersTips)showTips {
|
||||||
|
|
||||||
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
|
if (showTips & MPUsersThanksTip)
|
||||||
|
self.thanksTipContainer.alpha = 1;
|
||||||
|
if (showTips & MPUsersAvatarTip)
|
||||||
|
self.avatarTipContainer.alpha = 1;
|
||||||
|
if (showTips & MPUsersMasterPasswordTip)
|
||||||
|
self.entryTipContainer.alpha = 1;
|
||||||
|
if (showTips & MPUsersPreferencesTip)
|
||||||
|
self.preferencesTipContainer.alpha = 1;
|
||||||
|
} completion:^(BOOL finished) {
|
||||||
|
if (finished)
|
||||||
|
PearlMainQueueAfter( 5, ^{
|
||||||
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
|
if (showTips & MPUsersThanksTip)
|
||||||
|
self.thanksTipContainer.alpha = 0;
|
||||||
|
if (showTips & MPUsersAvatarTip)
|
||||||
|
self.avatarTipContainer.alpha = 0;
|
||||||
|
if (showTips & MPUsersMasterPasswordTip)
|
||||||
|
self.entryTipContainer.alpha = 0;
|
||||||
|
if (showTips & MPUsersPreferencesTip)
|
||||||
|
self.preferencesTipContainer.alpha = 0;
|
||||||
|
}];
|
||||||
|
} );
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)showEntryTip:(NSString *)message {
|
- (void)showEntryTip:(NSString *)message {
|
||||||
|
|
||||||
NSUInteger newlineIndex = [message rangeOfString:@"\n"].location;
|
NSUInteger newlineIndex = [message rangeOfString:@"\n"].location;
|
||||||
@@ -455,17 +508,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
NSString *messageSubtitle = newlineIndex == NSNotFound? nil: [message substringFromIndex:newlineIndex];
|
NSString *messageSubtitle = newlineIndex == NSNotFound? nil: [message substringFromIndex:newlineIndex];
|
||||||
self.entryTipTitleLabel.text = messageTitle;
|
self.entryTipTitleLabel.text = messageTitle;
|
||||||
self.entryTipSubtitleLabel.text = messageSubtitle;
|
self.entryTipSubtitleLabel.text = messageSubtitle;
|
||||||
|
[self showTips:MPUsersMasterPasswordTip];
|
||||||
[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 {
|
- (void)firedMarqueeTimer:(NSTimer *)timer {
|
||||||
@@ -474,15 +517,15 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
if ([nextMarqueeString isEqualToString:[self.marqueeButton titleForState:UIControlStateNormal]])
|
if ([nextMarqueeString isEqualToString:[self.marqueeButton titleForState:UIControlStateNormal]])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[UIView animateWithDuration:timer? 0.5: 0 animations:^{
|
[UIView animateWithDuration:timer? 0.5f: 0 animations:^{
|
||||||
self.marqueeButton.alpha = 0;
|
self.marqueeButton.alpha = 0;
|
||||||
} completion:^(BOOL finished) {
|
} completion:^(BOOL finished) {
|
||||||
if (!finished)
|
if (!finished)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[self.marqueeButton setTitle:nextMarqueeString forState:UIControlStateNormal];
|
[self.marqueeButton setTitle:nextMarqueeString forState:UIControlStateNormal];
|
||||||
[UIView animateWithDuration:timer? 0.5: 0 animations:^{
|
[UIView animateWithDuration:timer? 0.5f: 0 animations:^{
|
||||||
self.marqueeButton.alpha = 0.5;
|
self.marqueeButton.alpha = 0.5f;
|
||||||
}];
|
}];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -585,50 +628,33 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
|
|
||||||
- (void)registerObservers {
|
- (void)registerObservers {
|
||||||
|
|
||||||
if ([_notificationObservers count])
|
[self removeKeyPathObservers];
|
||||||
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:
|
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
||||||
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
|
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
|
||||||
[_self updateAvatarVisibility];
|
[_self updateAvatarVisibility];
|
||||||
}];
|
}];
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeObservers {
|
PearlRemoveNotificationObservers();
|
||||||
|
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
||||||
for (id observer in _notificationObservers)
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
self.userSelectionContainer.alpha = 0;
|
||||||
_notificationObservers = nil;
|
} );
|
||||||
|
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
|
||||||
[self removeKeyPathObservers];
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
}
|
[self reloadUsers];
|
||||||
|
} );
|
||||||
- (void)observeStore {
|
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
Weakify( self );
|
[UIView animateWithDuration:0.5f animations:^{
|
||||||
|
self.userSelectionContainer.alpha = 1;
|
||||||
|
}];
|
||||||
|
} );
|
||||||
|
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
|
||||||
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
|
CGRect keyboardRect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||||
|
CGFloat keyboardHeight = CGRectGetHeight( self.view.window.screen.bounds ) - CGRectGetMinY( keyboardRect );
|
||||||
|
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
|
||||||
|
} );
|
||||||
|
|
||||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||||
[UIView animateWithDuration:0.3f animations:^{
|
[UIView animateWithDuration:0.3f animations:^{
|
||||||
@@ -639,11 +665,9 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
if (!mainContext && !self.storeLoadingActivity.isAnimating)
|
if (!mainContext && !self.storeLoadingActivity.isAnimating)
|
||||||
[self.storeLoadingActivity startAnimating];
|
[self.storeLoadingActivity startAnimating];
|
||||||
|
|
||||||
if (!_mocObserver && mainContext)
|
if (mainContext)
|
||||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, mainContext, [NSOperationQueue mainQueue],
|
||||||
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
|
||||||
Strongify( self );
|
|
||||||
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
|
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
|
||||||
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
|
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
|
||||||
if ([[NSSetUnion( insertedObjects, deletedObjects )
|
if ([[NSSetUnion( insertedObjects, deletedObjects )
|
||||||
@@ -651,39 +675,21 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
return [evaluatedObject isKindOfClass:[MPUserEntity class]];
|
return [evaluatedObject isKindOfClass:[MPUserEntity class]];
|
||||||
}]] count])
|
}]] count])
|
||||||
[self reloadUsers];
|
[self reloadUsers];
|
||||||
}];
|
} );
|
||||||
if (!_storeChangingObserver)
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, [NSOperationQueue mainQueue],
|
||||||
_storeChangingObserver = [[NSNotificationCenter defaultCenter]
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification object:nil
|
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
|
||||||
Strongify( self );
|
|
||||||
if (self->_mocObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver];
|
|
||||||
self.userIDs = nil;
|
self.userIDs = nil;
|
||||||
}];
|
} );
|
||||||
if (!_storeChangedObserver)
|
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, [NSOperationQueue mainQueue],
|
||||||
_storeChangedObserver = [[NSNotificationCenter defaultCenter]
|
^(MPUsersViewController *self, NSNotification *note) {
|
||||||
addObserverForName:NSPersistentStoreCoordinatorStoresDidChangeNotification object:nil
|
[self registerObservers];
|
||||||
queue:nil usingBlock:^(NSNotification *note) {
|
|
||||||
Strongify( self );
|
|
||||||
[self reloadUsers];
|
[self reloadUsers];
|
||||||
}];
|
} );
|
||||||
}
|
|
||||||
|
|
||||||
- (void)stopObservingStore {
|
|
||||||
|
|
||||||
if (_mocObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
|
||||||
if (_storeChangingObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
|
|
||||||
if (_storeChangedObserver)
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reloadUsers {
|
- (void)reloadUsers {
|
||||||
|
|
||||||
[self afterUpdatesMainQueue:^{
|
[self afterUpdatesMainQueue:^{
|
||||||
[self observeStore];
|
|
||||||
if (![MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
if (![MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||||
@@ -692,7 +698,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
];
|
];
|
||||||
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
|
||||||
if (!users) {
|
if (!users) {
|
||||||
err( @"Failed to load users: %@", error );
|
err( @"Failed to load users: %@", [error fullDescription] );
|
||||||
self.userIDs = nil;
|
self.userIDs = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,11 +713,6 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
|
|
||||||
#pragma mark - Properties
|
#pragma mark - Properties
|
||||||
|
|
||||||
- (void)setActive:(BOOL)active {
|
|
||||||
|
|
||||||
[self setActive:active animated:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setActive:(BOOL)active animated:(BOOL)animated {
|
- (void)setActive:(BOOL)active animated:(BOOL)animated {
|
||||||
|
|
||||||
_active = active;
|
_active = active;
|
||||||
@@ -827,6 +828,29 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manage tip visibility.
|
||||||
|
switch (activeUserState) {
|
||||||
|
case MPActiveUserStateNone:
|
||||||
|
case MPActiveUserStateMasterPasswordConfirmation:
|
||||||
|
case MPActiveUserStateLogin: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPActiveUserStateUserName: {
|
||||||
|
[self showTips:MPUsersAvatarTip];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPActiveUserStateMasterPasswordChoice: {
|
||||||
|
[self showEntryTip:strl( @"A short phrase makes a strong, memorable password." )];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPActiveUserStateMinimized: {
|
||||||
|
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"tipped.passwordsPreferences"])
|
||||||
|
[self showTips:MPUsersPreferencesTip];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[self.view layoutIfNeeded];
|
[self.view layoutIfNeeded];
|
||||||
} completion:^(BOOL finished) {
|
} completion:^(BOOL finished) {
|
||||||
[_afterUpdates setSuspended:NO];
|
[_afterUpdates setSuspended:NO];
|
||||||
|
|||||||
@@ -50,6 +50,9 @@
|
|||||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||||
navigationType:(UIWebViewNavigationType)navigationType {
|
navigationType:(UIWebViewNavigationType)navigationType {
|
||||||
|
|
||||||
|
if ([[request.URL absoluteString] rangeOfString:@"thanks.lhunath.com"].location != NSNotFound)
|
||||||
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.thanks"];
|
||||||
|
|
||||||
if ([request.URL isEqual:request.mainDocumentURL]) {
|
if ([request.URL isEqual:request.mainDocumentURL]) {
|
||||||
self.webNavigationItem.title = request.URL.host;
|
self.webNavigationItem.title = request.URL.host;
|
||||||
self.webNavigationItem.prompt = strl( @"Loading" );
|
self.webNavigationItem.prompt = strl( @"Loading" );
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#import <MessageUI/MessageUI.h>
|
|
||||||
|
|
||||||
#import "MPAppDelegate_Shared.h"
|
#import "MPAppDelegate_Shared.h"
|
||||||
|
|
||||||
|
|||||||
@@ -10,27 +10,27 @@
|
|||||||
#import "MPAppDelegate_Key.h"
|
#import "MPAppDelegate_Key.h"
|
||||||
#import "MPAppDelegate_Store.h"
|
#import "MPAppDelegate_Store.h"
|
||||||
#import "IASKSettingsReader.h"
|
#import "IASKSettingsReader.h"
|
||||||
|
#import "MPStoreViewController.h"
|
||||||
|
|
||||||
@interface MPiOSAppDelegate()
|
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
|
||||||
|
|
||||||
|
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
|
||||||
|
|
||||||
@property(nonatomic, weak) PearlAlert *handleCloudDisabledAlert;
|
|
||||||
@property(nonatomic, weak) PearlAlert *handleCloudContentAlert;
|
|
||||||
@property(nonatomic, weak) PearlAlert *fixCloudContentAlert;
|
|
||||||
@property(nonatomic, weak) PearlOverlay *storeLoadingOverlay;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPiOSAppDelegate
|
@implementation MPiOSAppDelegate
|
||||||
|
|
||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
|
|
||||||
if ([self class] == [MPiOSAppDelegate class]) {
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once( &once, ^{
|
||||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
[PearlLogger get].printLevel = PearlLogLevelDebug; //Trace;
|
[PearlLogger get].printLevel = PearlLogLevelDebug; //Trace;
|
||||||
#else
|
#else
|
||||||
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||||
#endif
|
#endif
|
||||||
}
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
@@ -70,18 +70,15 @@
|
|||||||
err( @"During Analytics Setup: %@", exception );
|
err( @"During Analytics Setup: %@", exception );
|
||||||
}
|
}
|
||||||
@try {
|
@try {
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue]
|
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue], ^(id self, NSNotification *note) {
|
||||||
usingBlock:^(NSNotification *note) {
|
|
||||||
[self updateConfigKey:note.object];
|
[self updateConfigKey:note.object];
|
||||||
}];
|
} );
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:
|
PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
|
||||||
^(NSNotification *note) {
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
|
||||||
}];
|
} );
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil queue:nil usingBlock:
|
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) {
|
||||||
^(NSNotification *note) {
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
|
||||||
}];
|
} );
|
||||||
|
|
||||||
#ifdef ADHOC
|
#ifdef ADHOC
|
||||||
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
||||||
@@ -107,8 +104,7 @@
|
|||||||
@try {
|
@try {
|
||||||
inf( @"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier] );
|
inf( @"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier] );
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPFoundInconsistenciesNotification object:nil queue:nil usingBlock:
|
PearlAddNotificationObserver( MPFoundInconsistenciesNotification, nil, nil, ^(id self, NSNotification *note) {
|
||||||
^(NSNotification *note) {
|
|
||||||
switch ((MPFixableResult)[note.userInfo[MPInconsistenciesFixResultUserKey] unsignedIntegerValue]) {
|
switch ((MPFixableResult)[note.userInfo[MPInconsistenciesFixResultUserKey] unsignedIntegerValue]) {
|
||||||
|
|
||||||
case MPFixableResultNoProblems:
|
case MPFixableResultNoProblems:
|
||||||
@@ -128,13 +124,21 @@
|
|||||||
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}];
|
} );
|
||||||
|
|
||||||
PearlMainQueue( ^{
|
PearlMainQueue( ^{
|
||||||
if ([[MPiOSConfig get].showSetup boolValue])
|
if ([[MPiOSConfig get].showSetup boolValue])
|
||||||
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
|
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
NSString *latestFeatures = [MPStoreViewController latestStoreFeatures];
|
||||||
|
if (latestFeatures)
|
||||||
|
[PearlAlert showAlertWithTitle:@"New Features" message:
|
||||||
|
strf( @"The following features are now available in the store:\n\n%@•••\n\n"
|
||||||
|
@"Find the store from the user pull‑down after logging in.", latestFeatures )
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
|
||||||
|
cancelTitle:@"Thanks" otherTitles:nil];
|
||||||
|
|
||||||
MPCheckpoint( MPCheckpointStarted, @{
|
MPCheckpoint( MPCheckpointStarted, @{
|
||||||
@"simulator" : PearlStringB( [PearlDeviceUtils isSimulator] ),
|
@"simulator" : PearlStringB( [PearlDeviceUtils isSimulator] ),
|
||||||
@"encrypted" : PearlStringB( [PearlDeviceUtils isAppEncrypted] ),
|
@"encrypted" : PearlStringB( [PearlDeviceUtils isAppEncrypted] ),
|
||||||
@@ -168,13 +172,34 @@
|
|||||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||||
returningResponse:&response error:&error];
|
returningResponse:&response error:&error];
|
||||||
if (error)
|
if (error)
|
||||||
err( @"While reading imported sites from %@: %@", url, error );
|
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
|
||||||
if (!importedSitesData)
|
if (!importedSitesData) {
|
||||||
|
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@", [error localizedDescription]?: error )];
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
|
|
||||||
|
|
||||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||||
|
if (!importedSitesString) {
|
||||||
|
[PearlAlert showError:@"Master Password couldn't understand the import file."];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self importSites:importedSitesString];
|
||||||
|
} );
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)importSites:(NSString *)importedSitesString {
|
||||||
|
|
||||||
|
if ([NSThread isMainThread]) {
|
||||||
|
PearlNotMainQueue( ^{
|
||||||
|
[self importSites:importedSitesString];
|
||||||
|
} );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
|
||||||
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
||||||
__block NSString *masterPassword = nil;
|
__block NSString *masterPassword = nil;
|
||||||
|
|
||||||
@@ -245,9 +270,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[activityOverlay cancelOverlayAnimated:YES];
|
[activityOverlay cancelOverlayAnimated:YES];
|
||||||
} );
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
||||||
@@ -257,13 +279,13 @@
|
|||||||
[super applicationDidReceiveMemoryWarning:application];
|
[super applicationDidReceiveMemoryWarning:application];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
|
|
||||||
inf( @"Will deactivate" );
|
inf( @"Will background" );
|
||||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||||
[self signOutAnimated:NO];
|
[self signOutAnimated:NO];
|
||||||
|
|
||||||
[super applicationWillResignActive:application];
|
[super applicationDidEnterBackground:application];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||||
@@ -271,6 +293,21 @@
|
|||||||
inf( @"Re-activated" );
|
inf( @"Re-activated" );
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
|
||||||
|
|
||||||
|
NSString *importHeader = @"# Master Password site export";
|
||||||
|
NSString *importedSitesString = [UIPasteboard generalPasteboard].string;
|
||||||
|
if ([importedSitesString length] > [importHeader length] &&
|
||||||
|
[[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
|
||||||
|
[PearlAlert showAlertWithTitle:@"Import Sites?" message:
|
||||||
|
@"We've detected Master Password import sites on your pasteboard, would you like to import them?"
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||||
|
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == [alert cancelButtonIndex])
|
||||||
|
return;
|
||||||
|
|
||||||
|
[self importSites:importedSitesString];
|
||||||
|
[UIPasteboard generalPasteboard].string = nil;
|
||||||
|
} cancelTitle:@"No" otherTitles:@"Import Sites", nil];
|
||||||
|
|
||||||
[super applicationDidBecomeActive:application];
|
[super applicationDidBecomeActive:application];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +376,26 @@
|
|||||||
showComposerForVC:viewController];
|
showComposerForVC:viewController];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)handleCoordinatorError:(NSError *)error {
|
||||||
|
|
||||||
|
static dispatch_once_t once = 0;
|
||||||
|
dispatch_once( &once, ^{
|
||||||
|
[PearlAlert showAlertWithTitle:@"Failed To Load Sites" message:
|
||||||
|
@"Master Password was unable to open your sites history.\n"
|
||||||
|
@"This may be due to corruption. You can either reset Master Password and "
|
||||||
|
@"recreate your user, or E-Mail us your logs and leave your corrupt store as-is for now."
|
||||||
|
viewStyle:UIAlertViewStyleDefault initAlert:nil
|
||||||
|
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == [alert cancelButtonIndex])
|
||||||
|
return;
|
||||||
|
if (buttonIndex == [alert firstOtherButtonIndex])
|
||||||
|
[self openFeedbackWithLogs:YES forVC:nil];
|
||||||
|
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
|
||||||
|
[self deleteAndResetStore];
|
||||||
|
} cancelTitle:@"Ignore" otherTitles:@"E-Mail Logs", @"Reset", nil];
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
- (void)showExportForVC:(UIViewController *)viewController {
|
- (void)showExportForVC:(UIViewController *)viewController {
|
||||||
|
|
||||||
[PearlAlert showAlertWithTitle:@"Exporting Your Sites"
|
[PearlAlert showAlertWithTitle:@"Exporting Your Sites"
|
||||||
@@ -409,12 +466,38 @@
|
|||||||
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
|
||||||
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
|
||||||
|
|
||||||
|
NSString *exportFileName = strf( @"%@ (%@).mpsites",
|
||||||
|
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
|
||||||
|
[PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil
|
||||||
|
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == [sheet cancelButtonIndex])
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (buttonIndex == [sheet firstOtherButtonIndex]) {
|
||||||
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
||||||
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
|
attachments:[[PearlEMailAttachment alloc]
|
||||||
mimeType:@"text/plain" fileName:
|
initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
strf( @"%@ (%@).mpsites", [self activeUserForMainThread].name,
|
mimeType:@"text/plain" fileName:exportFileName],
|
||||||
[exportDateFormatter stringFromDate:[NSDate date]] )],
|
|
||||||
nil];
|
nil];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
|
||||||
|
inDomains:NSUserDomainMask] lastObject];
|
||||||
|
NSURL *exportURL = [[applicationSupportURL
|
||||||
|
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
|
||||||
|
URLByAppendingPathComponent:exportFileName isDirectory:NO];
|
||||||
|
NSError *error = nil;
|
||||||
|
if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
|
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error])
|
||||||
|
err( @"Failed to write export data to URL %@: %@", exportURL, [error fullDescription] );
|
||||||
|
else {
|
||||||
|
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
|
||||||
|
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
|
||||||
|
self.interactionController.delegate = self;
|
||||||
|
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
|
||||||
|
}
|
||||||
|
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
|
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
|
||||||
@@ -446,6 +529,13 @@
|
|||||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - UIDocumentInteractionControllerDelegate
|
||||||
|
|
||||||
|
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
|
||||||
|
|
||||||
|
// self.interactionController = nil;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - PearlConfigDelegate
|
#pragma mark - PearlConfigDelegate
|
||||||
|
|
||||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
|
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
|
||||||
|
|||||||
@@ -18,5 +18,9 @@
|
|||||||
@property(nonatomic, retain) NSNumber *loginNameTipShown;
|
@property(nonatomic, retain) NSNumber *loginNameTipShown;
|
||||||
@property(nonatomic, retain) NSNumber *traceMode;
|
@property(nonatomic, retain) NSNumber *traceMode;
|
||||||
@property(nonatomic, retain) NSNumber *dictationSearch;
|
@property(nonatomic, retain) NSNumber *dictationSearch;
|
||||||
|
@property(nonatomic, retain) NSNumber *developmentFuelRemaining;
|
||||||
|
@property(nonatomic, retain) NSNumber *developmentFuelInvested;
|
||||||
|
@property(nonatomic, retain) NSNumber *developmentFuelConsumption;
|
||||||
|
@property(nonatomic, retain) NSDate *developmentFuelChecked;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
@implementation MPiOSConfig
|
@implementation MPiOSConfig
|
||||||
|
|
||||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch;
|
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch;
|
||||||
|
@dynamic developmentFuelRemaining, developmentFuelInvested, developmentFuelConsumption, developmentFuelChecked;
|
||||||
|
|
||||||
- (id)init {
|
- (id)init {
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
NSStringFromSelector( @selector( typeTipShown ) ) : @(!self.firstRun),
|
NSStringFromSelector( @selector( typeTipShown ) ) : @(!self.firstRun),
|
||||||
NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO,
|
NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO,
|
||||||
NSStringFromSelector( @selector( traceMode ) ) : @NO,
|
NSStringFromSelector( @selector( traceMode ) ) : @NO,
|
||||||
NSStringFromSelector( @selector(dictationSearch) ) : @NO
|
NSStringFromSelector( @selector( dictationSearch ) ) : @NO,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|||||||
@@ -14,14 +14,16 @@
|
|||||||
<string>mpsites</string>
|
<string>mpsites</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeIconFiles</key>
|
<key>CFBundleTypeIconFiles</key>
|
||||||
<array/>
|
<array>
|
||||||
|
<string>Icon-Small</string>
|
||||||
|
</array>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>Master Password sites</string>
|
<string>Master Password sites</string>
|
||||||
<key>LSHandlerRank</key>
|
<key>LSHandlerRank</key>
|
||||||
<string>Owner</string>
|
<string>Owner</string>
|
||||||
<key>LSItemContentTypes</key>
|
<key>LSItemContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>com.lyndir.lhunath.MasterPassword.sites</string>
|
<string>com.lyndir.masterpassword.sites</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
@@ -39,25 +41,12 @@
|
|||||||
<string>[auto]</string>
|
<string>[auto]</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeRole</key>
|
|
||||||
<string>Editor</string>
|
|
||||||
<key>CFBundleURLName</key>
|
|
||||||
<string>com.lyndir.lhunath.MasterPassword</string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>com.lyndir.lhunath.MasterPassword</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>[auto]</string>
|
<string>[auto]</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© 2011-2013, Lyndir</string>
|
<string>© 2011-2014, Lyndir</string>
|
||||||
<key>UIAppFonts</key>
|
<key>UIAppFonts</key>
|
||||||
<array>
|
<array>
|
||||||
<string>Exo2.0-Bold.otf</string>
|
<string>Exo2.0-Bold.otf</string>
|
||||||
@@ -109,14 +98,18 @@
|
|||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.utf8-plain-text</string>
|
||||||
|
</array>
|
||||||
<key>UTTypeDescription</key>
|
<key>UTTypeDescription</key>
|
||||||
<string>Master Password sites</string>
|
<string>Master Password sites</string>
|
||||||
<key>UTTypeIdentifier</key>
|
<key>UTTypeIdentifier</key>
|
||||||
<string>com.lyndir.lhunath.MasterPassword.sites</string>
|
<string>com.lyndir.masterpassword.sites</string>
|
||||||
<key>UTTypeSize320IconFile</key>
|
<key>UTTypeSize320IconFile</key>
|
||||||
<string></string>
|
<string>Icon-320.png</string>
|
||||||
<key>UTTypeSize64IconFile</key>
|
<key>UTTypeSize64IconFile</key>
|
||||||
<string></string>
|
<string>Icon-64.png</string>
|
||||||
<key>UTTypeTagSpecification</key>
|
<key>UTTypeTagSpecification</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>public.filename-extension</key>
|
<key>public.filename-extension</key>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user