2
0

A few fixes to site import/export.

[FIXED]     A few core data issues.
[ADDED]     Avatar to export file.
[ADDED]     Generated site counter to export file.
[ADDED]     Site login name to export file.
This commit is contained in:
Maarten Billemont 2014-08-23 01:39:47 -04:00
parent a4fe13842a
commit c57bd5d5d3
8 changed files with 165 additions and 81 deletions

@ -1 +1 @@
Subproject commit 5e38f25f6e058cc52b8e41bcdc183b7966bb3ac7
Subproject commit 99dcbccee742d2bd2e5701f6b4e001138956030a

View File

@ -133,14 +133,14 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[[self mainManagedObjectContext] saveToStore];
}];
^(NSNotification *note) {
[[self mainManagedObjectContext] saveToStore];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[[self mainManagedObjectContext] saveToStore];
}];
^(NSNotification *note) {
[[self mainManagedObjectContext] saveToStore];
}];
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
queue:[NSOperationQueue mainQueue] usingBlock:
@ -405,11 +405,11 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
self.saveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:privateManagedObjectContext queue:nil usingBlock:
^(NSNotification *note) {
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}];
}];
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}];
}];
self.privateManagedObjectContext = privateManagedObjectContext;
self.mainManagedObjectContext = mainManagedObjectContext;
@ -532,7 +532,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
saveInContext:(NSManagedObjectContext *)context {
// Compile patterns.
static NSRegularExpression *headerPattern, *sitePattern;
static NSRegularExpression *headerPattern;
static NSArray *sitePatterns;
NSError *error = nil;
if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc]
@ -543,12 +544,17 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
return MPImportResultInternalError;
}
}
if (!sitePattern) {
sitePattern = [[NSRegularExpression alloc]
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error];
if (!sitePatterns) {
sitePatterns = @[
[[NSRegularExpression alloc] // Format 0
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error],
[[NSRegularExpression alloc] // Format 1
initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)"
options:(NSRegularExpressionOptions)0 error:&error]
];
if (error) {
err( @"Error loading the site pattern: %@", error );
err( @"Error loading the site patterns: %@", error );
return MPImportResultInternalError;
}
}
@ -557,6 +563,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
inf( @"Importing sites." );
__block MPUserEntity *user = nil;
id<MPAlgorithm> importAlgorithm = nil;
NSUInteger importFormat = 0;
NSUInteger importAvatar = NSNotFound;
NSString *importBundleVersion = nil, *importUserName = nil;
NSData *importKeyID = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
@ -604,8 +612,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
return MPImportResultInternalError;
}
user = [users count]? [users lastObject]: nil;
dbg( @"Found user: %@", [user debugDescription] );
user = [users lastObject];
dbg( @"Existing user? %@", [user debugDescription] );
}
if ([headerName isEqualToString:@"Key ID"])
importKeyID = [headerValue decodeHex];
@ -613,6 +621,15 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
importBundleVersion = headerValue;
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
}
if ([headerName isEqualToString:@"Format"]) {
importFormat = [headerValue integerValue];
if (importFormat >= [sitePatterns count]) {
err( @"Unsupported import format: %d", importFormat );
return MPImportResultInternalError;
}
}
if ([headerName isEqualToString:@"Avatar"])
importAvatar = [headerValue integerValue];
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
@ -628,6 +645,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
continue;
// Site
NSRegularExpression *sitePattern = sitePatterns[importFormat];
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid site format in line: %@", importedSiteLine );
@ -635,21 +653,45 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
}
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
switch (importFormat) {
case 0:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = @"";
loginName = @"";
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
break;
case 1:
lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
if ([counter length])
counter = [counter substringFromIndex:1]; // Strip the leading colon.
loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]];
exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]];
break;
default:
err( @"Unexpected import format: %d", importFormat );
return MPImportResultInternalError;
}
// Find existing site.
if (user) {
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error];
if (!existingSites) {
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error );
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, error );
return MPImportResultInternalError;
}
if ([existingSites count]) {
@ -657,14 +699,14 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
[elementsToDelete addObjectsFromArray:existingSites];
}
}
[importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]];
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@",
lastUsed, uses, type, version, name, exportContent );
[importedSiteElements addObject:@[ 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 );
}
// Ask for confirmation to import these sites and the master password of the user.
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count],
(unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] );
(unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
[elementsToDelete count] );
if (!userMasterPassword) {
@ -689,10 +731,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
}];
// Make sure there is a user.
if (!user) {
if (user) {
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Updating User: %@", [user debugDescription] );
} else {
user = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName;
user.keyID = importKeyID;
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] );
}
@ -702,13 +750,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
MPElementType type = (MPElementType)[siteElements[2] integerValue];
NSUInteger version = (unsigned)[siteElements[3] integerValue];
NSString *name = siteElements[4];
NSString *exportContent = siteElements[5];
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
NSString *siteName = siteElements[6];
NSString *exportContent = siteElements[7];
// Create new site.
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
element.name = name;
element.name = siteName;
element.loginName = loginName;
element.user = user;
element.type = type;
element.uses = uses;
@ -720,6 +771,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
else
[element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
}
if ([element isKindOfClass:[MPElementGeneratedEntity class]] && counter != NSNotFound)
((MPElementGeneratedEntity *)element).counter = counter;
dbg( @"Created Element: %@", [element debugDescription] );
}
@ -751,18 +804,20 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
[export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"# \n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# User Name: %@\n", activeUser.name];
[export appendFormat:@"# Avatar: %d\n", activeUser.avatar];
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Format: 1\n"];
if (revealPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"];
else
[export appendFormat:@"# Passwords: PROTECTED\n"];
[export appendFormat:@"##\n"];
[export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Site\tSite\n"];
[export appendFormat:@"# used used type name\tpassword\n"];
[export appendFormat:@"# Last Times Password Login\t Site\tSite\n"];
[export appendFormat:@"# used used type name\t name\tpassword\n"];
// Sites.
for (MPElementEntity *element in activeUser.elements) {
@ -770,9 +825,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
NSUInteger uses = element.uses;
MPElementType type = element.type;
NSUInteger version = element.version;
NSString *name = element.name;
NSUInteger counter = 0;
NSString *loginName = element.loginName;
NSString *siteName = element.name;
NSString *content = nil;
// Generated-specific
if ([element isKindOfClass:[MPElementGeneratedEntity class]])
counter = ((MPElementGeneratedEntity *)element).counter;
// Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) {
if (revealPasswords)
@ -781,10 +843,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
content = [element.algorithm exportContentForElement:element usingKey:self.key];
}
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
[export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
[strf( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content
? content: @""];
[strf( @"%lu:%lu:%lu", (long)type, (long)version, (long)counter ) UTF8String],
[(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""];
}
MPCheckpoint( MPCheckpointSitesExported, @{

View File

@ -20,7 +20,7 @@
@try {
NSError *error = nil;
if (!(success = [self save:&error]))
err( @"While saving: %@", error );
err( @"While saving: %@", [error fullDescription] );
}
@catch (NSException *exception) {
success = NO;

View File

@ -36,6 +36,5 @@
- (void)updatePasswords;
- (IBAction)dismissPopdown:(id)sender;
- (IBAction)signOut:(id)sender;
@end

View File

@ -32,7 +32,8 @@
@end
@implementation MPPasswordsViewController {
__weak id _storeObserver;
__weak id _storeChangingObserver;
__weak id _storeChangedObserver;
__weak id _mocObserver;
NSArray *_notificationObservers;
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
@ -275,7 +276,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
_fetchedResultsController = nil;
@ -293,8 +294,8 @@ referenceSizeForHeaderInSection:(NSInteger)section {
}];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
addObserverForName:MPCheckConfigNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateConfigKey:note.object];
}],
];
@ -314,17 +315,25 @@ referenceSizeForHeaderInSection:(NSInteger)section {
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// Strongify(self);
// [self updatePasswords];
addObserverForName:NSManagedObjectContextDidSaveNotification object:mainContext
queue:nil usingBlock:^(NSNotification *note) {
if (![[MPiOSAppDelegate get] activeUserInContext:mainContext])
[[MPiOSAppDelegate get] signOutAnimated:YES];
}];
if (!_storeObserver)
_storeObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
if (!_storeChangingObserver)
_storeChangingObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreWillChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
_fetchedResultsController = nil;
if (self->_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver];
}];
if (!_storeChangedObserver)
_storeChangedObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreDidChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
self->_fetchedResultsController = nil;
[self updatePasswords];
}];
}
@ -333,8 +342,10 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
if (_storeChangingObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
if (_storeChangedObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
}
- (void)updateConfigKey:(NSString *)key {
@ -350,8 +361,8 @@ referenceSizeForHeaderInSection:(NSInteger)section {
NSString *query = self.query;
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
if (!activeUserOID) {
self.passwordsSearchBar.text = nil;
PearlMainQueue( ^{
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData];
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES];
} );
@ -444,9 +455,4 @@ referenceSizeForHeaderInSection:(NSInteger)section {
self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
}
- (IBAction)signOut:(id)sender {
[[MPiOSAppDelegate get] signOutAnimated:YES];
}
@end

View File

@ -50,7 +50,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
@end
@implementation MPUsersViewController {
__weak id _storeObserver;
__weak id _storeChangingObserver;
__weak id _storeChangedObserver;
__weak id _mocObserver;
NSArray *_notificationObservers;
NSString *_masterPasswordChoice;
@ -382,6 +383,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
[context deleteObject:user_];
[context saveToStore];
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
}];
return;
}
@ -623,7 +625,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
queue:nil usingBlock:^(NSNotification *note) {
Strongify(self);
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
@ -633,10 +635,18 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
}]] count])
[self reloadUsers];
}];
if (!_storeObserver)
_storeObserver = [[NSNotificationCenter defaultCenter]
if (!_storeChangingObserver)
_storeChangingObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreWillChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify(self);
if (self->_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver];
}];
if (!_storeChangedObserver)
_storeChangedObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
queue:nil usingBlock:^(NSNotification *note) {
Strongify(self);
[self reloadUsers];
}];
@ -646,8 +656,10 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
if (_storeChangingObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
if (_storeChangedObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
}
- (void)reloadUsers {

View File

@ -597,9 +597,14 @@
if (buttonIndex == [alert cancelButtonIndex])
return;
[[self storeManager] migrateCloudToLocal];
if (buttonIndex == [alert firstOtherButtonIndex])
[UIApp openURL:[NSURL URLWithString:
@"http://support.lyndir.com/topic/486731-why-doesnt-the-mac-version-have-icloud-support/#comment-755394"]];
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[MPiOSConfig get].iCloudEnabled = @NO;
}
cancelTitle:@"Ignore For Now" otherTitles:@"Disable iCloud", nil];
cancelTitle:@"Ignore For Now" otherTitles:@"Why?", @"Disable iCloud", nil];
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager failedLoadingStoreWithCause:(UbiquityStoreErrorCause)cause context:(id)context

View File

@ -150,7 +150,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry" customClass="UIView+Touches">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry">
<rect key="frame" x="20" y="255" width="280" height="68"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
@ -236,7 +236,7 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XEP-O3-ayG" userLabel="Footer" customClass="UIView+Touches">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XEP-O3-ayG" userLabel="Footer">
<rect key="frame" x="0.0" y="477" width="320" height="71"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
@ -980,11 +980,11 @@
<viewControllerLayoutGuide type="top" id="S9X-2T-e1e"/>
<viewControllerLayoutGuide type="bottom" id="c12-XI-Rv9"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="MSX-uI-cwS" userLabel="Root" customClass="UIView+Touches">
<view key="view" contentMode="scaleToFill" id="MSX-uI-cwS" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tI8-OT-LrO" userLabel="Passwords Root" customClass="UIView+Touches">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tI8-OT-LrO" userLabel="Passwords Root">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>