2
0

Versioning + automatic user selection + misc UI tweaks.

[ADDED]     Versioning and explicit migration to MPElementEntity.
[ADDED]     Upgrade button in case the element needs explicit migration.
[ADDED]     Messages in Crashlytics and TestFlight logs upon
            initialization so we can easily see it worked and what the
            client's versioning looks like.
[IMPROVED]  Only show firstRun UI tooltips once.
[IMPROVED]  Automatically select the latest user upon load of unlock.
[IMPROVED]  Automatically select the user when his password is reset.
[IMPROVED]  Hide active element when logging a user out.
This commit is contained in:
Maarten Billemont 2012-07-12 08:41:18 +02:00
parent 6f37f28a4c
commit 217cf56d94
23 changed files with 225 additions and 101 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 6be4c8b6646c89fe341c40dac40a9accb6b73bbf
Subproject commit 5c0c968d29f2133e517e4d9432794998e1ce0d4c

View File

@ -25,9 +25,6 @@
DA3EF17D15A47744003ABF4E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA3EF18315A47744003ABF4E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA3EF18115A47744003ABF4E /* InfoPlist.strings */; };
DA3EF18615A47744003ABF4E /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3EF18515A47744003ABF4E /* Tests.m */; };
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2601586099D0079CE6E /* MPUserEntity.m */; };
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */; };
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */; };
DA4425CC1557BED40052177D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA4426001557BF260052177D /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DA4425F11557BF260052177D /* UbiquityStoreManager.h */; };
DA4426011557BF260052177D /* UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4425F21557BF260052177D /* UbiquityStoreManager.m */; };
@ -36,7 +33,6 @@
DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libiCloudStoreManager.a */; };
DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */; };
DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */; };
DA46827315ABF00100FB09E7 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA46827215ABF00100FB09E7 /* MPElementEntity.m */; };
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; };
DA4DA1DA1564471F00F6F596 /* libuicolor-utilities.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */; };
DA4DA1DB1564475E00F6F596 /* libscryptenc-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */; };
@ -676,6 +672,10 @@
DAB8D93815036BF700CED3BC /* lock_red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B515036BF600CED3BC /* lock_red@2x.png */; };
DAB8D93915036BF700CED3BC /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6B615036BF600CED3BC /* logo-bare.png */; };
DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */; };
DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */; };
DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */; };
DAB9FE2515AC00C0007A7E5C /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB9FE2415AC00C0007A7E5C /* MPElementEntity.m */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
@ -914,12 +914,6 @@
DA3EF18415A47744003ABF4E /* Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tests.h; sourceTree = "<group>"; };
DA3EF18515A47744003ABF4E /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
DA3EF18715A47744003ABF4E /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
DA40C25F1586099D0079CE6E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA40C2601586099D0079CE6E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DA4425CB1557BED40052177D /* libiCloudStoreManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libiCloudStoreManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
DA4425F11557BF260052177D /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
DA4425F21557BF260052177D /* UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UbiquityStoreManager.m; sourceTree = "<group>"; };
@ -930,8 +924,6 @@
DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = "<group>"; };
DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_bottom_right.png; sourceTree = "<group>"; };
DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_bottom_right@2x.png"; sourceTree = "<group>"; };
DA46827115ABF00100FB09E7 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DA46827215ABF00100FB09E7 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DA5BFA44147E415C00F98B1E /* MasterPassword.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MasterPassword.app; sourceTree = BUILT_PRODUCTS_DIR; };
DA5BFA48147E415C00F98B1E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@ -1648,6 +1640,14 @@
DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_teal@2x.png"; sourceTree = "<group>"; };
DAB8D6F815036BF600CED3BC /* tip_location_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_location_wood.png; sourceTree = "<group>"; };
DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_wood@2x.png"; sourceTree = "<group>"; };
DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DAB9FE2315AC00C0007A7E5C /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DAB9FE2415AC00C0007A7E5C /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DABB980C150FF40100B05417 /* SendToMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SendToMac-Prefix.pch"; sourceTree = "<group>"; };
DABB980D150FF40100B05417 /* SendToMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendToMac.h; sourceTree = "<group>"; };
DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = "<group>"; };
@ -1999,17 +1999,17 @@
DA5BFA50147E415C00F98B1E /* MasterPassword */ = {
isa = PBXGroup;
children = (
DA40C2681586099E0079CE6E /* MPElementStoredEntity.h */,
DA40C2691586099E0079CE6E /* MPElementStoredEntity.m */,
DA40C2651586099E0079CE6E /* MPElementGeneratedEntity.h */,
DA40C2661586099E0079CE6E /* MPElementGeneratedEntity.m */,
DA40C25F1586099D0079CE6E /* MPUserEntity.h */,
DA40C2601586099D0079CE6E /* MPUserEntity.m */,
DAB9FE2315AC00C0007A7E5C /* MPElementEntity.h */,
DAB9FE2415AC00C0007A7E5C /* MPElementEntity.m */,
DAB9FE2015AC00C0007A7E5C /* MPElementStoredEntity.h */,
DAB9FE2115AC00C0007A7E5C /* MPElementStoredEntity.m */,
DAB9FE1D15AC00C0007A7E5C /* MPUserEntity.h */,
DAB9FE1E15AC00C0007A7E5C /* MPUserEntity.m */,
DAB9FE1A15AC00C0007A7E5C /* MPElementGeneratedEntity.h */,
DAB9FE1B15AC00C0007A7E5C /* MPElementGeneratedEntity.m */,
DA0E07941577FE490008A67E /* MPEntities.h */,
DA0E07951577FE490008A67E /* MPEntities.m */,
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DA46827115ABF00100FB09E7 /* MPElementEntity.h */,
DA46827215ABF00100FB09E7 /* MPElementEntity.m */,
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@ -4245,10 +4245,10 @@
DA4426091557C1990052177D /* MPAppDelegate_Store.m in Sources */,
DA0E07961577FE490008A67E /* MPEntities.m in Sources */,
DAC728CA157C247B00889EF2 /* MPPreferencesViewController.m in Sources */,
DA40C2611586099D0079CE6E /* MPUserEntity.m in Sources */,
DA40C2671586099E0079CE6E /* MPElementGeneratedEntity.m in Sources */,
DA40C26A1586099E0079CE6E /* MPElementStoredEntity.m in Sources */,
DA46827315ABF00100FB09E7 /* MPElementEntity.m in Sources */,
DAB9FE1C15AC00C0007A7E5C /* MPElementGeneratedEntity.m in Sources */,
DAB9FE1F15AC00C0007A7E5C /* MPUserEntity.m in Sources */,
DAB9FE2215AC00C0007A7E5C /* MPElementStoredEntity.m in Sources */,
DAB9FE2515AC00C0007A7E5C /* MPElementEntity.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -19,6 +19,7 @@
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) MPUserEntity *user;
@end

View File

@ -18,6 +18,7 @@
@dynamic type_;
@dynamic uses_;
@dynamic version_;
@dynamic requiresExplicitMigration_;
@dynamic user;
@end

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Created by Maarten Billemont on 10/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Created by Maarten Billemont on 10/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Created by Maarten Billemont on 10/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Created by Maarten Billemont on 10/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -18,6 +18,8 @@
@property (assign) MPElementType type;
@property (assign) NSUInteger uses;
@property (assign) NSUInteger version;
@property (assign) BOOL requiresExplicitMigration;
- (NSUInteger)use;
- (NSString *)exportContent;

View File

@ -33,6 +33,25 @@
self.uses_ = PearlUnsignedInteger(anUses);
}
- (NSUInteger)version {
return [self.version_ unsignedIntegerValue];
}
- (void)setVersion:(NSUInteger)version {
self.version_ = PearlUnsignedInteger(version);
}
- (BOOL)requiresExplicitMigration {
return [self.requiresExplicitMigration_ boolValue];
}
- (void)setRequiresExplicitMigration:(BOOL)requiresExplicitMigration {
self.requiresExplicitMigration_ = PearlBool(requiresExplicitMigration);
}
- (NSUInteger)use {

View File

@ -8,7 +8,7 @@
#import <Foundation/Foundation.h>
#define MPPersistentStoreDidChangeNotification @"MPPersistentStoreDidChange"
@class MPElementEntity;
typedef enum {
MPElementContentTypePassword,
@ -70,12 +70,12 @@ typedef enum {
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
#define MPCheckpointExplicitMigration @"MPCheckpointExplicitMigration"
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationSignedIn @"MPNotificationKeySet"
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed"
#define MPNotificationElementUpdated @"MPNotificationElementUpdated"
NSData *keyForPassword(NSString *password, NSString *username);
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
@ -84,5 +84,6 @@ NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type);
NSString *NSStringShortFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);
void MPElementMigrate(MPElementEntity *element, BOOL explicit);

View File

@ -221,3 +221,11 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui
return content;
}
void MPElementMigrate(MPElementEntity *element, BOOL explicit) {
if (element.version == 0 && explicit) {
// 0 -> 1
element.version = 1;
}
}

View File

@ -2,7 +2,7 @@
// MPUserEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Created by Maarten Billemont on 10/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -14,11 +14,11 @@
@interface MPUserEntity : NSManagedObject
@property (nonatomic, retain) NSNumber * avatar_;
@property (nonatomic, retain) NSNumber * defaultType_;
@property (nonatomic, retain) NSData * keyID;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * defaultType_;
@property (nonatomic, retain) NSSet *elements;
@end

View File

@ -2,7 +2,7 @@
// MPUserEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 11/06/12.
// Created by Maarten Billemont on 10/07/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -13,11 +13,11 @@
@implementation MPUserEntity
@dynamic avatar_;
@dynamic defaultType_;
@dynamic keyID;
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic defaultType_;
@dynamic elements;
@end

View File

@ -4,9 +4,10 @@
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="version_" optional="YES" attributeType="Integer 16" 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"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
@ -25,7 +26,7 @@
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="165"/>
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>

View File

@ -74,6 +74,8 @@
return YES;
}];
TFLog(@"TestFlight (%@) initialized for: %@ v%@.", //
TESTFLIGHT_SDK_VERSION, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion);
}
}
@catch (id exception) {
@ -99,6 +101,8 @@
return YES;
}];
CLSLog(@"Crashlytics (%@) initialized for: %@ v%@.", //
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion);
}
}
@catch (id exception) {
@ -427,7 +431,7 @@
}
- (void)exportShowPasswords:(BOOL)showPasswords {
if (![MFMailComposeViewController canSendMail]) {
[PearlAlert showAlertWithTitle:@"Cannot Send Mail"
message:
@ -460,7 +464,7 @@
self.activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion);
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"];

View File

@ -23,6 +23,7 @@
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
@property (weak, nonatomic) IBOutlet UIButton *passwordUpgrade;
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@ -34,8 +35,8 @@
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
@property (weak, nonatomic) IBOutlet UIView *toolTipContainer;
@property (weak, nonatomic) IBOutlet UILabel *toolTipBody;
@property (weak, nonatomic) IBOutlet UIView *toolTipContainer;
@property (weak, nonatomic) IBOutlet UILabel *toolTipBody;
@property (copy) void (^contentTipCleanup)(BOOL finished);
@property (copy) void (^toolTipCleanup)(BOOL finished);
@ -45,6 +46,7 @@
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
- (IBAction)editPassword;
- (IBAction)closeAlert;
- (IBAction)upgradePassword;
- (IBAction)action:(UIBarButtonItem *)sender;
- (BOOL)isHelpVisible;

View File

@ -13,6 +13,8 @@
#import "LocalyticsSession.h"
void MPElementMigrate(MPElementEntity *entity, BOOL i);
@interface MPMainViewController (Private)
- (void)updateAnimated:(BOOL)animated;
@ -33,6 +35,7 @@
@synthesize passwordCounter = _passwordCounter;
@synthesize passwordIncrementer = _passwordIncrementer;
@synthesize passwordEdit = _passwordEdit;
@synthesize passwordUpgrade = _passwordUpgrade;
@synthesize contentContainer = _contentContainer;
@synthesize helpContainer = _helpContainer;
@synthesize contentTipContainer = _copiedContainer;
@ -70,26 +73,39 @@
}
- (void)viewDidLoad {
self.searchDelegate = [MPSearchDelegate new];
self.searchDelegate.delegate = self;
self.searchDelegate.searchDisplayController = self.searchDisplayController;
self.searchDelegate.searchTipContainer = self.searchTipContainer;
self.searchDisplayController.searchBar.delegate = self.searchDelegate;
self.searchDisplayController.delegate = self.searchDelegate;
self.searchDisplayController.searchResultsDelegate = self.searchDelegate;
self.searchDelegate = [MPSearchDelegate new];
self.searchDelegate.delegate = self;
self.searchDelegate.searchDisplayController = self.searchDisplayController;
self.searchDelegate.searchTipContainer = self.searchTipContainer;
self.searchDisplayController.searchBar.delegate = self.searchDelegate;
self.searchDisplayController.delegate = self.searchDelegate;
self.searchDisplayController.searchResultsDelegate = self.searchDelegate;
self.searchDisplayController.searchResultsDataSource = self.searchDelegate;
self.resetPasswordCounterGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(resetPasswordCounter:)];
[self.passwordIncrementer addGestureRecognizer:self.resetPasswordCounterGesture];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
self.contentField.font = [UIFont fontWithName:@"Exo-Black" size:self.contentField.font.pointSize];
self.alertBody.text = nil;
self.alertBody.text = nil;
self.toolTipEditIcon.hidden = YES;
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationElementUpdated object:nil queue:nil
usingBlock:^void(NSNotification *note) {
if (self.activeElement.type & MPElementTypeClassStored
&& ![[self.activeElement.content description] length])
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
if (self.activeElement.requiresExplicitMigration)
[self showToolTip:@"Password is outdated. Tap to upgrade it." withIcon:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^void(NSNotification *note) {
self.activeElement = nil;
}];
[super viewDidLoad];
}
@ -100,7 +116,7 @@
if (![MPAppDelegate get].activeUser)
[self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
animated:animated completion:nil];
if (self.activeElement.user != [MPAppDelegate get].activeUser)
self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil;
@ -108,6 +124,7 @@
self.searchTipContainer.alpha = 0;
self.actionsTipContainer.alpha = 0;
self.typeTipContainer.alpha = 0;
self.toolTipContainer.alpha = 0;
[self setHelpHidden:[[MPiOSConfig get].helpHidden boolValue] animated:animated];
[self updateAnimated:animated];
@ -117,11 +134,13 @@
- (void)viewDidAppear:(BOOL)animated {
if ([[MPiOSConfig get].firstRun boolValue])
if (![[MPiOSConfig get].actionsTipShown boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
self.actionsTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished) {
[MPiOSConfig get].actionsTipShown = PearlBool(YES);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.actionsTipContainer.alpha = 0;
@ -153,6 +172,7 @@
[self setPasswordCounter:nil];
[self setPasswordIncrementer:nil];
[self setPasswordEdit:nil];
[self setPasswordUpgrade:nil];
[self setContentContainer:nil];
[self setHelpContainer:nil];
[self setContentTipContainer:nil];
@ -183,9 +203,22 @@
[self setHelpChapter:self.activeElement? @"2": @"1"];
self.siteName.text = self.activeElement.name;
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
self.passwordCounter.alpha = 0;
self.passwordIncrementer.alpha = 0;
self.passwordEdit.alpha = 0;
self.passwordUpgrade.alpha = 0;
if (self.activeElement.requiresExplicitMigration)
self.passwordUpgrade.alpha = 0.5f;
else {
if (self.activeElement.type & MPElementTypeClassGenerated) {
self.passwordCounter.alpha = 0.5f;
self.passwordIncrementer.alpha = 0.5f;
} else
if (self.activeElement.type & MPElementTypeClassStored)
self.passwordEdit.alpha = 0.5f;
}
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
forState:UIControlStateNormal];
@ -254,17 +287,17 @@
}
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.contentTipCleanup)
self.contentTipCleanup(NO);
self.contentTipBody.text = message;
self.contentTipCleanup = ^(BOOL finished) {
icon.hidden = YES;
self.contentTipCleanup = nil;
};
icon.hidden = NO;
[UIView animateWithDuration:0.3f animations:^{
self.contentTipContainer.alpha = 1;
@ -282,17 +315,17 @@
}
- (void)showToolTip:(NSString *)message withIcon:(UIImageView *)icon {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.toolTipCleanup)
self.toolTipCleanup(NO);
self.toolTipBody.text = message;
self.toolTipCleanup = ^(BOOL finished) {
icon.hidden = YES;
icon.hidden = YES;
self.toolTipCleanup = nil;
};
icon.hidden = NO;
[UIView animateWithDuration:0.3f animations:^{
self.toolTipContainer.alpha = 1;
@ -363,7 +396,8 @@
[TestFlight passCheckpoint:MPCheckpointIncrementPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointIncrementPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}];
}
@ -391,7 +425,8 @@
[TestFlight passCheckpoint:MPCheckpointResetPasswordCounter];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointResetPasswordCounter
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(self.activeElement.type), @"type",
NSStringFromMPElementType(
self.activeElement.type), @"type",
nil]];
}];
}
@ -404,7 +439,7 @@
return;
[self changeElementWithoutWarningDo:task];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
@ -442,6 +477,31 @@
}
}
- (IBAction)upgradePassword {
[self changeElementWithWarning:
self.activeElement.type & MPElementTypeClassGenerated?
@"You are upgrading the site.\n\n"
@"This upgrade improves the site's compatibility with the latest version of Master Password.\n\n"
@"Your password will change and you will need to update your site's account."
:
@"You are upgrading the site.\n\n"
@"This upgrade improves the site's compatibility with the latest version of Master Password."
do:^{
inf(@"Explicitly migrating element: %@", self.activeElement);
MPElementMigrate(self.activeElement, YES);
[TestFlight passCheckpoint:MPCheckpointExplicitMigration];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointExplicitMigration
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
PearlUnsignedInteger(self.activeElement.version),
@"version",
nil]];
}];
}
- (IBAction)closeAlert {
[UIView animateWithDuration:0.3f animations:^{
@ -501,7 +561,7 @@
@"masterpassword@lyndir.com"
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
otherTitles:nil];
else {
[PearlAlert showAlertWithTitle:@"Sending Feedback"
@ -514,7 +574,8 @@
MFMailComposeViewController *composer = [MFMailComposeViewController new];
[composer setMailComposeDelegate:self];
[composer setToRecipients:[NSArray arrayWithObject:@"Master Password Development <masterpassword@lyndir.com>"]];
[composer setSubject:PearlString(@"Feedback for Master Password [%@]", [[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
[composer setSubject:PearlString(@"Feedback for Master Password [%@]",
[[PearlKeyChain deviceIdentifier] stringByDeletingMatchesOf:@"-.*"])];
[composer setMessageBody:
PearlString(
@"\n\n\n"
@ -524,20 +585,21 @@
[MPAppDelegate get].activeUser.name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion)
isHTML:NO];
isHTML:NO];
if (buttonIndex_ == [alert_ firstOtherButtonIndex]) {
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug
: PearlLogLevelInfo;
[composer addAttachmentData:[[[PearlLogger get] formatMessagesWithLevel:logLevel] dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:PearlString(@"%@-%@.log",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
[PearlKeyChain deviceIdentifier])];
mimeType:@"text/plain"
fileName:PearlString(@"%@-%@.log",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]],
[PearlKeyChain deviceIdentifier])];
}
[self presentModalViewController:composer animated:YES];
}
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
cancelTitle:nil otherTitles:@"Include Logs", @"No Logs", nil];
}
break;
}
@ -552,7 +614,7 @@
[TestFlight passCheckpoint:MPCheckpointAction];
}
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Preferences", @"Feedback", @"Sign Out", nil];
}
@ -597,14 +659,14 @@
self.activeElement.type = type;
if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated
object:self.activeElement];
}];
}
- (void)didSelectElement:(MPElementEntity *)element {
inf(@"Selected: %@, version: %@", element.name, element.version_);
inf(@"Selected: %@", element.name);
[self closeAlert];
@ -619,27 +681,31 @@
self.activeElement.name, self.activeElement.name)];
[[MPAppDelegate get] saveContext];
if ([[MPiOSConfig get].firstRun boolValue])
if (![[MPiOSConfig get].typeTipShown boolValue])
[UIView animateWithDuration:0.5f animations:^{
self.typeTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.typeTipContainer.alpha = 0;
}];
});
[MPiOSConfig get].typeTipShown = PearlBool(YES);
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2f animations:^{
self.typeTipContainer.alpha = 0;
}];
});
}
}];
[self.searchDisplayController setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUsed object:self.activeElement];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationElementUpdated object:self.activeElement];
[TestFlight passCheckpoint:PearlString(MPCheckpointUseType @"_%@", NSStringFromMPElementType(self.activeElement.type))];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointUseType attributes:[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromMPElementType(
self.activeElement.type), @"type",
self.activeElement.type),
@"type",
nil]];
}

View File

@ -155,6 +155,9 @@
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
if (!animated)
[[self findTargetedAvatar] setSelected:YES];
[super viewDidAppear:animated];
}
@ -617,10 +620,9 @@
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]];
if (!targetedUser)
return;
[PearlSheet showSheetWithTitle:targetedUser.name
message:nil
viewStyle:UIActionSheetStyleBlackTranslucent
message:nil viewStyle:UIActionSheetStyleBlackTranslucent
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
@ -629,8 +631,11 @@
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
[[MPAppDelegate get] saveContext];
[self updateUsers];
} else if (buttonIndex == [sheet firstOtherButtonIndex])
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
} else
if (buttonIndex == [sheet firstOtherButtonIndex]) {
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
[[self avatarForUser:targetedUser] setSelected:YES];
}
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
}
@end

View File

@ -13,6 +13,8 @@
@property (nonatomic, retain) NSNumber *sendInfo;
@property (nonatomic, retain) NSNumber *helpHidden;
@property (nonatomic, retain) NSNumber *showQuickStart;
@property (nonatomic, retain) NSNumber *actionsTipShown;
@property (nonatomic, retain) NSNumber *typeTipShown;
+ (MPiOSConfig *)get;

View File

@ -7,7 +7,7 @@
//
@implementation MPiOSConfig
@dynamic sendInfo, helpHidden, showQuickStart;
@dynamic sendInfo, helpHidden, showQuickStart, actionsTipShown, typeTipShown;
- (id)init {
@ -19,6 +19,8 @@
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
@"510296984", NSStringFromSelector(@selector(iTunesID)),
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(actionsTipShown)),
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(typeTipShown)),
nil]];
return self;

View File

@ -480,7 +480,7 @@ Your passwords will be AES-encrypted with your master password.</string>
<outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/>
</connections>
</textField>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="Iuf-np-e9C">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="Iuf-np-e9C" userLabel="Label - Counter">
<rect key="frame" x="230" y="31" width="41" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="17"/>
@ -489,7 +489,7 @@ Your passwords will be AES-encrypted with your master password.</string>
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt" userLabel="Button - Counter">
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt" userLabel="Button - Incrementer">
<rect key="frame" x="265" y="18.5" width="44" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<gestureRecognizers/>
@ -522,7 +522,7 @@ Your passwords will be AES-encrypted with your master password.</string>
<action selector="editPassword" destination="PQa-Xl-A3x" eventType="touchUpInside" id="jZC-Jm-ZZ0"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="zbY-EJ-Yiu" userLabel="Button - Upgrade">
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="zbY-EJ-Yiu" userLabel="Button - Upgrade">
<rect key="frame" x="265" y="18" width="44" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
@ -536,6 +536,7 @@ Your passwords will be AES-encrypted with your master password.</string>
</state>
<connections>
<action selector="editPassword" destination="PQa-Xl-A3x" eventType="touchUpInside" id="SSO-oQ-WBa"/>
<action selector="upgradePassword" destination="PQa-Xl-A3x" eventType="touchUpInside" id="0dr-HU-goH"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Cei-5z-uWE">
@ -683,7 +684,11 @@ L4m3P4sSw0rD</string>
<rect key="frame" x="93" y="10" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Password is outdated. Tap to upgrade it." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="MtZ-N7-CYD">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_up.png" id="Xxd-3E-PLb">
<rect key="frame" x="180" y="10" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Password is outdated. Tap to upgrade it." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="MtZ-N7-CYD">
<rect key="frame" x="-20" y="10" width="340" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -754,6 +759,7 @@ L4m3P4sSw0rD</string>
<outlet property="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
<outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
<outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/>
<outlet property="passwordUpgrade" destination="zbY-EJ-Yiu" id="KAX-Wk-DC4"/>
<outlet property="searchDisplayController" destination="P8c-gf-nN3" id="CLs-YI-7NC"/>
<outlet property="searchTipContainer" destination="zOR-Du-qRL" id="X7h-Vh-iCE"/>
<outlet property="siteName" destination="gSK-aB-wNI" id="IIe-z8-zy8"/>
@ -1389,6 +1395,7 @@ L4m3P4sSw0rD</string>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="upgradePassword"/>
<relationship kind="outlet" name="actionsTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
@ -1402,6 +1409,7 @@ L4m3P4sSw0rD</string>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordUpgrade" candidateClass="UIButton"/>
<relationship kind="outlet" name="resetPasswordCounterGesture" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="searchDelegate" candidateClass="MPSearchDelegate"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>

View File

@ -5,9 +5,10 @@
<head>
<title>Master Password &mdash; Secure your online identity and privacy.</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<link rel="icon" href="img/favicon.png" type="image/x-png" />
<link rel="shortcut icon" href="img/favicon.png" type="image/x-png" />
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<link rel="publisher" href="https://plus.google.com/116256327773442623984/about" />
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Exo:100,400,600,900,100italic,400italic,600italic" />
<link rel="stylesheet" type="text/css" href="css/ml-shadows.css" />
@ -116,8 +117,8 @@
/* ]]> */
</script>
<script type="text/javascript" src="http://www.googleadservices.com/pagead/conversion_async.js"></script>
</head>
<body id="frontpage">
<a class="appstore" href="http://itunes.apple.com/app/id510296984" onclick="goog_report_conversion('index-fixed-header');_gaq.push(['_trackPageview', '/outbound/itunes']);"><img src="img/appstore.png" /></a>
<header>
@ -294,6 +295,7 @@
<hr />
<a class="next" href="what.html">What is this thing and why do I need it?</a>
</section>
<span itemscope itemtype="http://schema.org/MobileSoftwareApplication">