Updated animations for activation of the passwords VC and fancier focussed user.
This commit is contained in:
parent
318aca4d8f
commit
d036b43d6f
2
External/LoveLyndir
vendored
2
External/LoveLyndir
vendored
@ -1 +1 @@
|
||||
Subproject commit 97eafd9b59f84bd9e3fc3cb4313df8b3c034e766
|
||||
Subproject commit 0b858644cb58c357e055b41c2747ad9d45fa9ce3
|
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit 575b409cca36eabfaacf0a963ed259454cb8ec66
|
||||
Subproject commit 673217287de2920d21ef84f1d6811d8250f187b4
|
@ -7,7 +7,7 @@ CF_EXTERN_C_BEGIN
|
||||
|
||||
/*!
|
||||
\brief The Reveal Log level bit flags.
|
||||
\discussion These flags are addative. Ie, you should bitwise OR them together.
|
||||
\discussion These flags are additive. i.e. you should bitwise OR them together.
|
||||
|
||||
\seealso IBARevealLoggerSetLevelMask
|
||||
\seealso IBARevealLoggerGetLevelMask
|
||||
|
BIN
External/Reveal.framework/Versions/A/Reveal
vendored
BIN
External/Reveal.framework/Versions/A/Reveal
vendored
Binary file not shown.
@ -5,13 +5,11 @@
|
||||
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>D4AB9F0C-D746-4319-AABF-B24705099AED</string>
|
||||
<string>3D68B2F1-988A-48C3-8450-B37D43BDFE92</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>MasterPassword</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>2B6DA448-3730-4F84-B2C3-51272E0D42F3</key>
|
||||
<string>ssh://github.com/lhunath/DCIntrospect.git</string>
|
||||
<key>5263993D-5FE8-464F-B66E-B0F7C2DFF410</key>
|
||||
<string>ssh://github.com/lhunath/UbiquityStoreManager.git</string>
|
||||
<key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key>
|
||||
@ -24,8 +22,10 @@
|
||||
<string>git://github.com/lhunath/uicolor-utilities.git</string>
|
||||
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
|
||||
<string>git://github.com/lhunath/RHStatusItemView.git</string>
|
||||
<key>CBA93B91-B799-4CC6-85B6-749792B76DD4</key>
|
||||
<string>ssh://github.com/lhunath/InAppSettingsKit.git</string>
|
||||
<key>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</key>
|
||||
<string>git://github.com/lhunath/InAppSettingsKit.git</string>
|
||||
<key>D5CE8AB8-2F69-4A08-A2CE-93C70E0F0567</key>
|
||||
<string>https://github.com/lhunath/DCIntrospect.git</string>
|
||||
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
|
||||
<string>git://github.com/jonmarimba/jrswizzle.git</string>
|
||||
<key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key>
|
||||
@ -35,8 +35,6 @@
|
||||
<string>MasterPassword.xcworkspace</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>2B6DA448-3730-4F84-B2C3-51272E0D42F3</key>
|
||||
<string>../External/DCIntrospect</string>
|
||||
<key>5263993D-5FE8-464F-B66E-B0F7C2DFF410</key>
|
||||
<string>../External/UbiquityStoreManager</string>
|
||||
<key>6A449EC2-A2A3-4635-9C5F-A811E011EAC3</key>
|
||||
@ -49,8 +47,10 @@
|
||||
<string>../External/Pearl/External/uicolor-utilities</string>
|
||||
<key>B0F634DD-AEE1-4F0D-AE35-4FAF51AD1B5A</key>
|
||||
<string>../External/RHStatusItemView</string>
|
||||
<key>CBA93B91-B799-4CC6-85B6-749792B76DD4</key>
|
||||
<key>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</key>
|
||||
<string>../External/InAppSettingsKit</string>
|
||||
<key>D5CE8AB8-2F69-4A08-A2CE-93C70E0F0567</key>
|
||||
<string>../External/DCIntrospect</string>
|
||||
<key>E4C8E206-229C-4DA8-A130-0C544DEC7E07</key>
|
||||
<string>../External/Pearl/External/jrswizzle</string>
|
||||
<key>FF42A9E0-F41C-42FC-88CD-F2CCDE15DBB6</key>
|
||||
@ -68,7 +68,7 @@
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>2B6DA448-3730-4F84-B2C3-51272E0D42F3</string>
|
||||
<string>D5CE8AB8-2F69-4A08-A2CE-93C70E0F0567</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>DCIntrospect</string>
|
||||
</dict>
|
||||
@ -84,7 +84,7 @@
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>CBA93B91-B799-4CC6-85B6-749792B76DD4</string>
|
||||
<string>CDDE92CF-0136-4DE0-8318-80EDB5C8CAF9</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>InAppSettingsKit</string>
|
||||
</dict>
|
||||
|
@ -26,15 +26,21 @@ extern const long MPAvatarAdd;
|
||||
typedef NS_ENUM(NSUInteger, MPAvatarMode) {
|
||||
MPAvatarModeLowered,
|
||||
MPAvatarModeRaisedButInactive,
|
||||
MPAvatarModeRaisedAndActive
|
||||
MPAvatarModeRaisedAndActive,
|
||||
MPAvatarModeRaisedAndHidden,
|
||||
MPAvatarModeRaisedAndMinimized,
|
||||
};
|
||||
|
||||
@interface MPAvatarCell : UICollectionViewCell
|
||||
@property (copy, nonatomic) NSString *name;
|
||||
@property (assign, nonatomic) long avatar;
|
||||
@property (assign, nonatomic) MPAvatarMode mode;
|
||||
@property (assign, nonatomic) float visibility;
|
||||
@property (assign, nonatomic) CGFloat visibility;
|
||||
@property (assign, nonatomic) BOOL spinnerActive;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
|
||||
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated;
|
||||
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
@ -25,7 +25,10 @@ const long MPAvatarAdd = 10000;
|
||||
@property(strong, nonatomic) IBOutlet UIImageView *avatarImageView;
|
||||
@property(strong, nonatomic) IBOutlet UILabel *nameLabel;
|
||||
@property(strong, nonatomic) IBOutlet UIView *nameContainer;
|
||||
@property(strong, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *nameCenterConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarSizeConstraint;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarTopConstraint;
|
||||
|
||||
@end
|
||||
|
||||
@ -37,6 +40,8 @@ const long MPAvatarAdd = 10000;
|
||||
return @"MPAvatarCell";
|
||||
}
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
- (void)awakeFromNib {
|
||||
|
||||
[super awakeFromNib];
|
||||
@ -45,28 +50,46 @@ const long MPAvatarAdd = 10000;
|
||||
|
||||
self.avatarImageView.hidden = NO;
|
||||
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
|
||||
self.avatarImageView.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
self.avatarImageView.layer.shadowOpacity = 1;
|
||||
self.avatarImageView.layer.shadowRadius = 15;
|
||||
self.avatarImageView.layer.masksToBounds = NO;
|
||||
self.avatarImageView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||
[_self onSelectedOrHighlighted];
|
||||
[_self updateAnimated:YES];
|
||||
}];
|
||||
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||
[_self onSelectedOrHighlighted];
|
||||
[_self updateAnimated:YES];
|
||||
}];
|
||||
|
||||
self.visibility = 0;
|
||||
self.mode = MPAvatarModeLowered;
|
||||
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
toShadowOpacityAnimation.toValue = @0.2f;
|
||||
toShadowOpacityAnimation.duration = 0.5f;
|
||||
|
||||
CABasicAnimation *pulseShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
|
||||
pulseShadowOpacityAnimation.fromValue = @0.2f;
|
||||
pulseShadowOpacityAnimation.toValue = @0.6f;
|
||||
pulseShadowOpacityAnimation.beginTime = 0.5f;
|
||||
pulseShadowOpacityAnimation.duration = 2.0f;
|
||||
pulseShadowOpacityAnimation.autoreverses = YES;
|
||||
pulseShadowOpacityAnimation.repeatCount = MAXFLOAT;
|
||||
|
||||
CAAnimationGroup *group = [CAAnimationGroup new];
|
||||
group.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ];
|
||||
group.duration = MAXFLOAT;
|
||||
[self.avatarImageView.layer addAnimation:group forKey:@"targetedShadow"];
|
||||
self.avatarImageView.layer.shadowColor = [UIColor whiteColor].CGColor;
|
||||
self.avatarImageView.layer.shadowOffset = CGSizeZero;
|
||||
|
||||
[self setVisibility:0 animated:NO];
|
||||
[self setMode:MPAvatarModeLowered animated:NO];
|
||||
}
|
||||
|
||||
- (void)onSelectedOrHighlighted {
|
||||
- (void)dealloc {
|
||||
|
||||
self.avatarImageView.backgroundColor = self.selected || self.highlighted? self.avatarImageView.tintColor: [UIColor clearColor];
|
||||
[self removeKeyPathObservers];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setAvatar:(long)avatar {
|
||||
|
||||
_avatar = avatar;
|
||||
@ -87,11 +110,16 @@ const long MPAvatarAdd = 10000;
|
||||
self.nameLabel.text = name;
|
||||
}
|
||||
|
||||
- (void)setVisibility:(float)visibility {
|
||||
- (void)setVisibility:(CGFloat)visibility {
|
||||
|
||||
[self setVisibility:visibility animated:YES];
|
||||
}
|
||||
|
||||
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated {
|
||||
|
||||
_visibility = visibility;
|
||||
|
||||
self.nameContainer.alpha = visibility;
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted {
|
||||
@ -105,35 +133,117 @@ const long MPAvatarAdd = 10000;
|
||||
|
||||
- (void)setMode:(MPAvatarMode)mode {
|
||||
|
||||
[self setMode:mode animated:YES];
|
||||
}
|
||||
|
||||
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated {
|
||||
|
||||
_mode = mode;
|
||||
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)setSpinnerActive:(BOOL)spinnerActive {
|
||||
|
||||
[self setSpinnerActive:spinnerActive animated:YES];
|
||||
}
|
||||
|
||||
- (void)setSpinnerActive:(BOOL)spinnerActive animated:(BOOL)animated {
|
||||
|
||||
_spinnerActive = spinnerActive;
|
||||
|
||||
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
|
||||
rotate.toValue = [NSNumber numberWithDouble:2 * M_PI];
|
||||
rotate.duration = 5.0;
|
||||
|
||||
if (spinnerActive) {
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
rotate.fromValue = @0.0;
|
||||
rotate.repeatCount = MAXFLOAT;
|
||||
}
|
||||
else {
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
rotate.repeatCount = 1;
|
||||
}
|
||||
|
||||
[self.spinner.layer removeAnimationForKey:@"rotation"];
|
||||
[self.spinner.layer addAnimation:rotate forKey:@"rotation"];
|
||||
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated {
|
||||
|
||||
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
|
||||
self.avatarImageView.transform = CGAffineTransformIdentity;
|
||||
}];
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
switch (mode) {
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
switch (self.mode) {
|
||||
|
||||
case MPAvatarModeLowered: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameContainer.alpha = self.visibility;
|
||||
self.nameContainer.backgroundColor = [UIColor clearColor];
|
||||
self.avatarImageView.alpha = 1;
|
||||
self.avatarImageView.alpha = self.visibility / 0.7f + 0.3f;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedButInactive: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameContainer.alpha = self.visibility;
|
||||
self.nameContainer.backgroundColor = [UIColor clearColor];
|
||||
self.avatarImageView.alpha = 0.3f;
|
||||
self.avatarImageView.alpha = 0;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedAndActive: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameContainer.alpha = self.visibility;
|
||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||
self.avatarImageView.alpha = 1;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedAndHidden: {
|
||||
self.avatarSizeConstraint.constant = self.avatarImageView.image.size.height;
|
||||
self.avatarTopConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.nameCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameContainer.alpha = 0;
|
||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||
self.avatarImageView.alpha = 0;
|
||||
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
||||
break;
|
||||
}
|
||||
case MPAvatarModeRaisedAndMinimized: {
|
||||
self.avatarSizeConstraint.constant = 36;
|
||||
self.avatarTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.nameContainer.alpha = 0;
|
||||
self.nameContainer.backgroundColor = [UIColor blackColor];
|
||||
self.avatarImageView.alpha = 1;
|
||||
self.avatarImageView.layer.shadowOpacity = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self.avatarSizeConstraint apply];
|
||||
[self.avatarTopConstraint apply];
|
||||
[self.nameCenterConstraint apply];
|
||||
|
||||
// Avatar selection and spinner.
|
||||
if (self.mode != MPAvatarModeRaisedAndMinimized && (self.selected || self.highlighted) && !self.spinnerActive)
|
||||
self.avatarImageView.backgroundColor = self.avatarImageView.tintColor;
|
||||
else
|
||||
self.avatarImageView.backgroundColor = [UIColor clearColor];
|
||||
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
|
||||
self.spinner.alpha = self.spinnerActive? 1: 0;
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -16,34 +16,18 @@
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLGitTip.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPCombinedMode) {
|
||||
MPCombinedModeUserSelection,
|
||||
MPCombinedModePasswordSelection,
|
||||
};
|
||||
|
||||
@interface MPCombinedViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UITextFieldDelegate>
|
||||
@interface MPCombinedViewController : UIViewController
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIView *usersView;
|
||||
@property (strong, nonatomic) IBOutlet UIView *passwordsView;
|
||||
|
||||
@property(assign, nonatomic) MPCombinedMode mode;
|
||||
|
||||
#pragma mark - UserSelection
|
||||
|
||||
@property(strong, nonatomic) IBOutlet UIView *userSelectionContainer;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *hintLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIView *gitTipTip;
|
||||
@property(weak, nonatomic) IBOutlet LLGitTip *gitTipButton;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *entryField;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
|
||||
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
|
||||
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *avatarCollectionCenterConstraint;
|
||||
|
||||
#pragma mark - PasswordSelection
|
||||
|
||||
@property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer;
|
||||
@property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView;
|
||||
|
||||
- (IBAction)doSignOut:(UIBarButtonItem *)sender;
|
||||
|
||||
@end
|
||||
|
@ -17,45 +17,35 @@
|
||||
//
|
||||
|
||||
#import "MPCombinedViewController.h"
|
||||
#import "MPEntities.h"
|
||||
#import "MPAvatarCell.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
MPActiveUserStateNone,
|
||||
MPActiveUserStateLogin,
|
||||
MPActiveUserStateUserName,
|
||||
MPActiveUserStateMasterPasswordChoice,
|
||||
MPActiveUserStateMasterPasswordConfirmation,
|
||||
};
|
||||
#import "MPUsersViewController.h"
|
||||
#import "MPPasswordsViewController.h"
|
||||
|
||||
@interface MPCombinedViewController()
|
||||
|
||||
@property(nonatomic) MPActiveUserState activeUserState;
|
||||
@property(nonatomic, strong) NSArray *userIDs;
|
||||
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsTopConstraint;
|
||||
@property(nonatomic, strong) MPUsersViewController *usersVC;
|
||||
@property(nonatomic, strong) MPPasswordsViewController *passwordsVC;
|
||||
@end
|
||||
|
||||
@implementation MPCombinedViewController {
|
||||
__weak id _storeObserver;
|
||||
__weak id _mocObserver;
|
||||
NSArray *_notificationObservers;
|
||||
NSString *_masterPasswordChoice;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
self.avatarCollectionView.allowsMultipleSelection = YES;
|
||||
[self setMode:MPCombinedModeUserSelection animated:NO];
|
||||
}
|
||||
|
||||
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
||||
^(id from, id to, NSKeyValueChange cause, MPCombinedViewController *_self) {
|
||||
[_self updateAvatars];
|
||||
}];
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
self.mode = MPCombinedModeUserSelection;
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[[self navigationController] setNavigationBarHidden:YES animated:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
@ -63,7 +53,6 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[self registerObservers];
|
||||
[self updateMode];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
@ -71,353 +60,60 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self needStoreObserved:NO];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
if ([segue.identifier isEqualToString:@"users"])
|
||||
self.usersVC = segue.destinationViewController;
|
||||
if ([segue.identifier isEqualToString:@"passwords"])
|
||||
self.passwordsVC = segue.destinationViewController;
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
|
||||
if (textField == self.entryField) {
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
[textField resignFirstResponder];
|
||||
return UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setMode:(MPCombinedMode)mode {
|
||||
|
||||
[self setMode:mode animated:YES];
|
||||
}
|
||||
|
||||
- (void)setMode:(MPCombinedMode)mode animated:(BOOL)animated {
|
||||
|
||||
_mode = mode;
|
||||
|
||||
[self becomeFirstResponder];
|
||||
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
switch (self.mode) {
|
||||
case MPCombinedModeUserSelection: {
|
||||
[self.usersVC setActive:YES animated:NO];
|
||||
[self.passwordsVC setActive:NO animated:NO];
|
||||
// MPUsersViewController *usersVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MPUsersViewController"];
|
||||
// [self setViewControllers:@[ usersVC ] direction:UIPageViewControllerNavigationDirectionReverse
|
||||
// animated:animated completion:nil];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin: {
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL signedIn = NO, isNew = NO;
|
||||
MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew];
|
||||
if (!isNew && user)
|
||||
signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context
|
||||
usingMasterPassword:self.entryField.text];
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
if (!signedIn) {
|
||||
// Sign in failed.
|
||||
// TODO: warn user
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateUserName: {
|
||||
NSString *userName = self.entryField.text;
|
||||
if (![userName length]) {
|
||||
// No name entered.
|
||||
// TODO: warn user
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self selectedAvatar].name = userName;
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice: {
|
||||
NSString *masterPassword = self.entryField.text;
|
||||
if (![masterPassword length]) {
|
||||
// No password entered.
|
||||
// TODO: warn user
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordConfirmation;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
NSString *masterPassword = self.entryField.text;
|
||||
if (![masterPassword length]) {
|
||||
// No password entered.
|
||||
// TODO: warn user
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![masterPassword isEqualToString:_masterPasswordChoice]) {
|
||||
// Master password confirmation failed.
|
||||
// TODO: warn user
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
return NO;
|
||||
}
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew];
|
||||
if (isNew) {
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
||||
inManagedObjectContext:context];
|
||||
MPAvatarCell *avatarCell = [self selectedAvatar];
|
||||
user.avatar = avatarCell.avatar;
|
||||
user.name = avatarCell.name;
|
||||
}
|
||||
|
||||
BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context usingMasterPassword:masterPassword];
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
if (!signedIn) {
|
||||
// Sign in failed, shouldn't happen for a new user.
|
||||
// TODO: warn user
|
||||
self.activeUserState = MPActiveUserStateNone;
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
||||
case MPCombinedModePasswordSelection: {
|
||||
[self.usersVC setActive:NO animated:NO];
|
||||
[self.passwordsVC setActive:YES animated:NO];
|
||||
// MPPasswordsViewController *passwordsVC = [self.storyboard instantiateViewControllerWithIdentifier:@"MPPasswordsViewController"];
|
||||
// [self setViewControllers:@[ passwordsVC ] direction:UIPageViewControllerNavigationDirectionForward
|
||||
// animated:animated completion:nil];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
[self.passwordsTopConstraint apply];
|
||||
}];
|
||||
}
|
||||
|
||||
// This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification.
|
||||
- (void)textFieldDidChange:(UITextField *)textField {
|
||||
|
||||
if (textField == self.entryField) {
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone:
|
||||
break;
|
||||
case MPActiveUserStateLogin:
|
||||
break;
|
||||
case MPActiveUserStateUserName: {
|
||||
NSString *userName = self.entryField.text;
|
||||
[self selectedAvatar].name = [userName length]? userName: strl( @"New User" );
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
break;
|
||||
case MPActiveUserStateMasterPasswordConfirmation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
if (collectionView == self.avatarCollectionView)
|
||||
return [self.userIDs count] + 1;
|
||||
|
||||
else if (collectionView == self.passwordCollectionView)
|
||||
return 0;
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
MPAvatarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPAvatarCell reuseIdentifier] forIndexPath:indexPath];
|
||||
[cell addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPress:)]];
|
||||
[self updateAvatar:cell atIndexPath:indexPath];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]
|
||||
isNew:&isNew];
|
||||
if (isNew) {
|
||||
// New User
|
||||
cell.avatar = MPAvatarAdd;
|
||||
cell.name = strl( @"New User" );
|
||||
}
|
||||
else {
|
||||
// Existing User
|
||||
cell.avatar = user.avatar;
|
||||
cell.name = user.name;
|
||||
}
|
||||
|
||||
NSArray *selectedIndexPaths = [self.avatarCollectionView indexPathsForSelectedItems];
|
||||
if (![selectedIndexPaths count])
|
||||
cell.mode = MPAvatarModeLowered;
|
||||
else if ([selectedIndexPaths containsObject:indexPath])
|
||||
cell.mode = MPAvatarModeRaisedAndActive;
|
||||
else
|
||||
cell.mode = MPAvatarModeRaisedButInactive;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
else if (collectionView == self.passwordCollectionView)
|
||||
return nil;
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
[self.avatarCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
|
||||
animated:YES];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
for (NSUInteger otherItem = 0; otherItem < [collectionView numberOfItemsInSection:indexPath.section]; ++otherItem)
|
||||
if (otherItem != indexPath.item) {
|
||||
NSIndexPath *otherIndexPath = [NSIndexPath indexPathForItem:otherItem inSection:indexPath.section];
|
||||
[collectionView deselectItemAtIndexPath:otherIndexPath animated:YES];
|
||||
|
||||
MPAvatarCell *otherCell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:otherIndexPath];
|
||||
otherCell.mode = MPAvatarModeRaisedButInactive;
|
||||
}
|
||||
|
||||
MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
|
||||
cell.mode = MPAvatarModeRaisedAndActive;
|
||||
}];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]
|
||||
isNew:&isNew];
|
||||
if (isNew)
|
||||
self.activeUserState = MPActiveUserStateUserName;
|
||||
else if (!user.keyID)
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
else
|
||||
self.activeUserState = MPActiveUserStateLogin;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
self.activeUserState = MPActiveUserStateNone;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UILongPressGestureRecognizer
|
||||
|
||||
- (void)didLongPress:(UILongPressGestureRecognizer *)recognizer {
|
||||
|
||||
if ([recognizer.view isKindOfClass:[MPAvatarCell class]]) {
|
||||
if (recognizer.state != UIGestureRecognizerStateBegan)
|
||||
// Don't show the action menu unless the state is Began.
|
||||
return;
|
||||
|
||||
MPAvatarCell *avatarCell = (MPAvatarCell *)recognizer.view;
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForAvatar:avatarCell inContext:mainContext isNew:&isNew];
|
||||
NSManagedObjectID *userID = user.objectID;
|
||||
if (isNew || !user)
|
||||
return;
|
||||
|
||||
[PearlSheet showSheetWithTitle:user.name
|
||||
viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
// Delete User
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSManagedObject *user_ = [context existingObjectWithID:userID error:NULL];
|
||||
if (user_) {
|
||||
[context deleteObject:user_];
|
||||
[context saveToStore];
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
// Reset Password
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user_ = (MPUserEntity *)[context existingObjectWithID:userID error:NULL];
|
||||
if (user_)
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
|
||||
dbg(@"changing mp for user: %@, keyID: %@", user_.name, user_.keyID);
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
|
||||
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
|
||||
scrollPosition:UICollectionViewScrollPositionNone];
|
||||
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
|
||||
targetContentOffset:(inout CGPoint *)targetContentOffset {
|
||||
|
||||
if (scrollView == self.avatarCollectionView) {
|
||||
CGPoint offsetToCenter = CGPointMake(
|
||||
self.avatarCollectionView.bounds.size.width / 2,
|
||||
self.avatarCollectionView.bounds.size.height / 2 );
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForItemAtPoint:
|
||||
CGPointPlusCGPoint( *targetContentOffset, offsetToCenter )];
|
||||
CGPoint targetCenter = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:avatarIndexPath].center;
|
||||
*targetContentOffset = CGPointMinusCGPoint( targetCenter, offsetToCenter );
|
||||
NSAssert([self.avatarCollectionView indexPathForItemAtPoint:targetCenter].item == avatarIndexPath.item, @"should be same item");
|
||||
}
|
||||
}
|
||||
|
||||
- (MPAvatarCell *)selectedAvatar {
|
||||
|
||||
NSArray *selectedIndexPaths = self.avatarCollectionView.indexPathsForSelectedItems;
|
||||
if (![selectedIndexPaths count]) {
|
||||
// No selected user.
|
||||
return nil;
|
||||
}
|
||||
|
||||
return (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:selectedIndexPaths.firstObject];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)selectedUserInContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
MPAvatarCell *selectedAvatar = [self selectedAvatar];
|
||||
if (!selectedAvatar) {
|
||||
// No selected user.
|
||||
*isNew = NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self userForAvatar:selectedAvatar inContext:context isNew:isNew];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForAvatar:(MPAvatarCell *)cell inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
return [self userForIndexPath:[self.avatarCollectionView indexPathForCell:cell] inContext:context isNew:isNew];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForIndexPath:(NSIndexPath *)indexPath inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
if ((*isNew = indexPath.item >= [self.userIDs count]))
|
||||
return nil;
|
||||
|
||||
NSError *error = nil;
|
||||
MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:self.userIDs[indexPath.item] error:&error];
|
||||
if (error)
|
||||
wrn(@"Failed to load user into context: %@", error);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
- (void)updateAvatars {
|
||||
|
||||
for (NSIndexPath *indexPath in self.avatarCollectionView.indexPathsForVisibleItems)
|
||||
[self updateAvatarAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)updateAvatarAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
|
||||
[self updateAvatar:cell atIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)updateAvatar:(MPAvatarCell *)cell atIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
CGFloat current = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:indexPath].center.x -
|
||||
self.avatarCollectionView.contentOffset.x;
|
||||
CGFloat max = self.avatarCollectionView.bounds.size.width;
|
||||
cell.visibility = MAX(0, MIN( 1, 1 - ABS( current / (max / 2) - 1 ) ));
|
||||
}
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
@ -426,46 +122,19 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
// [self emergencyCloseAnimated:NO];
|
||||
self.userSelectionContainer.alpha = 0;
|
||||
self.passwordSelectionContainer.alpha = 0;
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self updateMode];
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.userSelectionContainer.alpha = 1;
|
||||
self.passwordSelectionContainer.alpha = 1;
|
||||
}];
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:MPSignedInNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
self.mode = MPCombinedModePasswordSelection;
|
||||
[self setMode:MPCombinedModePasswordSelection];
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:MPSignedOutNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
self.mode = MPCombinedModeUserSelection;
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UITextFieldTextDidChangeNotification object:self.entryField
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self textFieldDidChange:note.object];
|
||||
[self setMode:MPCombinedModeUserSelection animated:[note.userInfo[@"animated"] boolValue]];
|
||||
}],
|
||||
];
|
||||
}
|
||||
@ -477,181 +146,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
_notificationObservers = nil;
|
||||
}
|
||||
|
||||
- (void)needStoreObserved:(BOOL)observeStore {
|
||||
|
||||
if (observeStore) {
|
||||
Weakify(self);
|
||||
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!_mocObserver && mainContext)
|
||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
[self updateMode];
|
||||
}];
|
||||
if (!_storeObserver)
|
||||
_storeObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
[self updateMode];
|
||||
}];
|
||||
}
|
||||
if (!observeStore) {
|
||||
if (_mocObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
||||
if (_storeObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setUserIDs:(NSArray *)userIDs {
|
||||
|
||||
_userIDs = userIDs;
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self.avatarCollectionView reloadData];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setMode:(MPCombinedMode)mode {
|
||||
|
||||
_mode = mode;
|
||||
|
||||
[self updateMode];
|
||||
}
|
||||
|
||||
- (void)updateMode {
|
||||
|
||||
// Ensure we're on the main thread.
|
||||
if (![NSThread isMainThread]) {
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self updateMode];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
self.userSelectionContainer.hidden = YES;
|
||||
self.passwordSelectionContainer.hidden = YES;
|
||||
|
||||
[self becomeFirstResponder];
|
||||
|
||||
switch (self.mode) {
|
||||
case MPCombinedModeUserSelection: {
|
||||
[[self navigationController] setNavigationBarHidden:YES animated:YES];
|
||||
self.userSelectionContainer.hidden = NO;
|
||||
[self needStoreObserved:YES];
|
||||
|
||||
[self setActiveUserState:MPActiveUserStateNone animated:NO];
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
|
||||
if (!users) {
|
||||
err(@"Failed to load users: %@", error);
|
||||
self.userIDs = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *userIDs = [NSMutableArray arrayWithCapacity:[users count]];
|
||||
for (MPUserEntity *user in users)
|
||||
[userIDs addObject:user.objectID];
|
||||
self.userIDs = userIDs;
|
||||
}];
|
||||
|
||||
break;
|
||||
}
|
||||
case MPCombinedModePasswordSelection: {
|
||||
[[self navigationController] setNavigationBarHidden:NO animated:YES];
|
||||
self.passwordSelectionContainer.hidden = NO;
|
||||
[self needStoreObserved:NO];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setActiveUserState:(MPActiveUserState)activeUserState {
|
||||
|
||||
[self setActiveUserState:activeUserState animated:YES];
|
||||
}
|
||||
|
||||
- (void)setActiveUserState:(MPActiveUserState)activeUserState animated:(BOOL)animated {
|
||||
|
||||
_activeUserState = activeUserState;
|
||||
_masterPasswordChoice = nil;
|
||||
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
// Set the entry container's contents.
|
||||
switch (activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
for (NSUInteger item = 0; item < [self.avatarCollectionView numberOfItemsInSection:0]; ++item) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:0];
|
||||
[self.avatarCollectionView deselectItemAtIndexPath:indexPath animated:YES];
|
||||
|
||||
MPAvatarCell *avatarCell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
|
||||
avatarCell.mode = MPAvatarModeLowered;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin: {
|
||||
self.entryField.text = strl( @"Enter your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateUserName: {
|
||||
self.entryLabel.text = strl( @"Enter your full name:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = NO;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice: {
|
||||
self.entryLabel.text = strl( @"Choose your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
_masterPasswordChoice = self.entryField.text;
|
||||
self.entryLabel.text = strl( @"Confirm your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the random avatar for the new user if selected.
|
||||
MPAvatarCell *selectedAvatar = [self selectedAvatar];
|
||||
if (selectedAvatar.avatar == MPAvatarAdd) {
|
||||
selectedAvatar.avatar = arc4random() % MPAvatarCount;
|
||||
}
|
||||
else {
|
||||
NSIndexPath *newUserIndexPath = [NSIndexPath indexPathForItem:[_userIDs count] inSection:0];
|
||||
MPAvatarCell *newUserAvatar = (MPAvatarCell *)[[self avatarCollectionView] cellForItemAtIndexPath:newUserIndexPath];
|
||||
newUserAvatar.avatar = MPAvatarAdd;
|
||||
newUserAvatar.name = strl( @"New User" );
|
||||
}
|
||||
|
||||
// Manage the entry container depending on whether a user is activate or not.
|
||||
if (activeUserState == MPActiveUserStateNone) {
|
||||
self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.entryContainer.alpha = 0;
|
||||
}
|
||||
else {
|
||||
self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.entryContainer.alpha = 1;
|
||||
}
|
||||
[self.avatarCollectionCenterConstraint apply];
|
||||
|
||||
// Toggle the keyboard.
|
||||
if (activeUserState == MPActiveUserStateNone)
|
||||
[self.entryField resignFirstResponder];
|
||||
} completion:^(BOOL finished) {
|
||||
if (activeUserState != MPActiveUserStateNone)
|
||||
[self.entryField becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)doSignOut:(UIBarButtonItem *)sender {
|
||||
|
||||
|
33
MasterPassword/ObjC/iOS/MPPasswordsViewController.h
Normal file
33
MasterPassword/ObjC/iOS/MPPasswordsViewController.h
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLGitTip.h"
|
||||
|
||||
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDataSource, UITableViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UITextFieldDelegate>
|
||||
|
||||
@property(strong, nonatomic) IBOutlet UIView *passwordSelectionContainer;
|
||||
@property(strong, nonatomic) IBOutlet UICollectionView *passwordCollectionView;
|
||||
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
|
||||
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
|
||||
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToPasswordsConstraint;
|
||||
|
||||
@property(assign, nonatomic) BOOL active;
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated;
|
||||
|
||||
@end
|
218
MasterPassword/ObjC/iOS/MPPasswordsViewController.m
Normal file
218
MasterPassword/ObjC/iOS/MPPasswordsViewController.m
Normal file
@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPPasswordsViewController.h
|
||||
// MPPasswordsViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPasswordsViewController.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@interface MPPasswordsViewController()
|
||||
@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPPasswordsViewController {
|
||||
__weak id _storeObserver;
|
||||
__weak id _mocObserver;
|
||||
NSArray *_notificationObservers;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self registerObservers];
|
||||
[self observeStore];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self stopObservingStore];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
|
||||
if (tableView == self.searchDisplayController.searchResultsTableView)
|
||||
return 0;
|
||||
|
||||
NSAssert(NO, @"Unexpected table view: %@", tableView);
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (tableView == self.searchDisplayController.searchResultsTableView) {
|
||||
|
||||
}
|
||||
|
||||
NSAssert(NO, @"Unexpected table view: %@", tableView);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification.
|
||||
- (void)textFieldDidChange:(UITextField *)textField {
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
if (collectionView == self.passwordCollectionView)
|
||||
return 0;
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.passwordCollectionView)
|
||||
return nil;
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - UILongPressGestureRecognizer
|
||||
|
||||
- (void)didLongPress:(UILongPressGestureRecognizer *)recognizer {
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
if ([_notificationObservers count])
|
||||
return;
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
self.passwordSelectionContainer.alpha = 0;
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
// [self updateMode]; TODO: reload passwords list
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.passwordSelectionContainer.alpha = 1;
|
||||
}];
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
|
||||
for (id observer in _notificationObservers)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
_notificationObservers = nil;
|
||||
}
|
||||
|
||||
- (void)observeStore {
|
||||
|
||||
// Weakify(self);
|
||||
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!_mocObserver && mainContext)
|
||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
// Strongify(self);
|
||||
// [self updateMode]; TODO: reload passwords list
|
||||
}];
|
||||
if (!_storeObserver)
|
||||
_storeObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
// Strongify(self);
|
||||
// [self updateMode]; TODO: reload passwords list
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopObservingStore {
|
||||
|
||||
if (_mocObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
||||
if (_storeObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setActive:(BOOL)active {
|
||||
|
||||
[self setActive:active animated:YES];
|
||||
}
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated {
|
||||
|
||||
_active = active;
|
||||
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
self.navigationBarToPasswordsConstraint.priority = active? UILayoutPriorityDefaultHigh: 1;
|
||||
self.navigationBarToTopConstraint.priority = active? 1: UILayoutPriorityDefaultHigh;
|
||||
self.passwordsToBottomConstraint.priority = active? 1: UILayoutPriorityDefaultHigh;
|
||||
|
||||
[self.navigationBarToPasswordsConstraint apply];
|
||||
[self.navigationBarToTopConstraint apply];
|
||||
[self.passwordsToBottomConstraint apply];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
@end
|
@ -25,12 +25,12 @@
|
||||
|
||||
for (NSUInteger a = 0; a < MPAvatarCount; ++a) {
|
||||
UIButton *avatar = [self.avatarTemplate clone];
|
||||
avatar.tag = (NSInteger)a;
|
||||
avatar.tag = a;
|
||||
avatar.hidden = NO;
|
||||
avatar.center = CGPointMake(
|
||||
self.avatarTemplate.center.x * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
|
||||
self.avatarTemplate.center.y );
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%d", a )]
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%ld", (long)a )]
|
||||
forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
|
||||
|
||||
|
@ -54,7 +54,7 @@
|
||||
avatar.center = CGPointMake(
|
||||
(20 + self.avatarTemplate.bounds.size.width / 2) * (a + 1) + self.avatarTemplate.bounds.size.width / 2 * a,
|
||||
20 + self.avatarTemplate.bounds.size.height / 2 );
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%d", a )] forState:UIControlStateNormal];
|
||||
[avatar setBackgroundImage:[UIImage imageNamed:PearlString( @"avatar-%ld", (long)a )] forState:UIControlStateNormal];
|
||||
[avatar setSelectionInSuperviewCandidate:YES isClearable:NO];
|
||||
|
||||
avatar.layer.cornerRadius = avatar.bounds.size.height / 2;
|
||||
@ -276,12 +276,6 @@
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"MP_Settings"])
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
|
||||
return YES;
|
||||
@ -292,6 +286,12 @@
|
||||
return UIStatusBarAnimationSlide;
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([segue.identifier isEqualToString:@"MP_Settings"])
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
|
40
MasterPassword/ObjC/iOS/MPUsersViewController.h
Normal file
40
MasterPassword/ObjC/iOS/MPUsersViewController.h
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLGitTip.h"
|
||||
|
||||
@interface MPUsersViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UITextFieldDelegate>
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
|
||||
@property(weak, nonatomic) IBOutlet UIView *userSelectionContainer;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *hintLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIView *gitTipTip;
|
||||
@property(weak, nonatomic) IBOutlet LLGitTip *gitTipButton;
|
||||
@property(weak, nonatomic) IBOutlet UITextField *entryField;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
|
||||
@property(weak, nonatomic) IBOutlet UIView *footerContainer;
|
||||
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
|
||||
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *avatarCollectionCenterConstraint;
|
||||
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
|
||||
|
||||
@property(assign, nonatomic) BOOL active;
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated;
|
||||
|
||||
@end
|
742
MasterPassword/ObjC/iOS/MPUsersViewController.m
Normal file
742
MasterPassword/ObjC/iOS/MPUsersViewController.m
Normal file
@ -0,0 +1,742 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPCombinedViewController.h
|
||||
// MPCombinedViewController
|
||||
//
|
||||
// Created by lhunath on 2014-03-08.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPUsersViewController.h"
|
||||
#import "MPEntities.h"
|
||||
#import "MPAvatarCell.h"
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPActiveUserState) {
|
||||
/** The users are all inactive */
|
||||
MPActiveUserStateNone,
|
||||
/** The selected user is activated and being logged in with */
|
||||
MPActiveUserStateLogin,
|
||||
/** The selected user is activated and its user name is being asked for */
|
||||
MPActiveUserStateUserName,
|
||||
/** The selected user is activated and its new master password is being asked for */
|
||||
MPActiveUserStateMasterPasswordChoice,
|
||||
/** The selected user is activated and the confirmation of the previously entered master password is being asked for */
|
||||
MPActiveUserStateMasterPasswordConfirmation,
|
||||
/** The selected user is activated displayed at the top with the rest of the UI inactive */
|
||||
MPActiveUserStateMinimized,
|
||||
};
|
||||
|
||||
@interface MPUsersViewController()
|
||||
|
||||
@property(nonatomic) MPActiveUserState activeUserState;
|
||||
@property(nonatomic, strong) NSArray *userIDs;
|
||||
@property(nonatomic, strong) NSTimer *marqueeTipTimer;
|
||||
@property(nonatomic, strong) NSArray *marqueeTipTexts;
|
||||
@property(nonatomic) NSUInteger marqueeTipTextIndex;
|
||||
@end
|
||||
|
||||
@implementation MPUsersViewController {
|
||||
__weak id _storeObserver;
|
||||
__weak id _mocObserver;
|
||||
NSArray *_notificationObservers;
|
||||
NSString *_masterPasswordChoice;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
self.marqueeTipTexts = @[
|
||||
strl(@"Press and hold to change password or delete."),
|
||||
strl(@"Shake for emergency generator."),
|
||||
];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
self.avatarCollectionView.allowsMultipleSelection = YES;
|
||||
[self.entryField addTarget:self action:@selector(textFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged];
|
||||
|
||||
[self setActive:YES animated:NO];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.userSelectionContainer.alpha = 0;
|
||||
|
||||
[self observeStore];
|
||||
[self registerObservers];
|
||||
[self reloadUsers];
|
||||
|
||||
[self.marqueeTipTimer invalidate];
|
||||
self.marqueeTipTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(firedMarqueeTimer:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self stopObservingStore];
|
||||
|
||||
[self.marqueeTipTimer invalidate];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
|
||||
// if (motion == UIEventSubtypeMotionShake)
|
||||
// [self emergencyOpenAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
if (textField == self.entryField) {
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
[textField resignFirstResponder];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin: {
|
||||
[self selectedAvatar].spinnerActive = YES;
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL signedIn = NO, isNew = NO;
|
||||
MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew];
|
||||
if (!isNew && user)
|
||||
signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context
|
||||
usingMasterPassword:self.entryField.text];
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self selectedAvatar].spinnerActive = NO;
|
||||
|
||||
if (!signedIn) {
|
||||
// Sign in failed.
|
||||
// TODO: warn user
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateUserName: {
|
||||
NSString *userName = self.entryField.text;
|
||||
if (![userName length]) {
|
||||
// No name entered.
|
||||
// TODO: warn user
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self selectedAvatar].name = userName;
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice: {
|
||||
NSString *masterPassword = self.entryField.text;
|
||||
if (![masterPassword length]) {
|
||||
// No password entered.
|
||||
// TODO: warn user
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordConfirmation;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
NSString *masterPassword = self.entryField.text;
|
||||
if (![masterPassword length]) {
|
||||
// No password entered.
|
||||
// TODO: warn user
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![masterPassword isEqualToString:_masterPasswordChoice]) {
|
||||
// Master password confirmation failed.
|
||||
// TODO: warn user
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self selectedAvatar].spinnerActive = YES;
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self selectedUserInContext:context isNew:&isNew];
|
||||
if (isNew) {
|
||||
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
||||
inManagedObjectContext:context];
|
||||
MPAvatarCell *avatarCell = [self selectedAvatar];
|
||||
user.avatar = avatarCell.avatar;
|
||||
user.name = avatarCell.name;
|
||||
}
|
||||
|
||||
BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context usingMasterPassword:masterPassword];
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self selectedAvatar].spinnerActive = NO;
|
||||
|
||||
if (!signedIn) {
|
||||
// Sign in failed, shouldn't happen for a new user.
|
||||
// TODO: warn user
|
||||
self.activeUserState = MPActiveUserStateNone;
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}];
|
||||
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized: {
|
||||
[textField resignFirstResponder];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification.
|
||||
- (void)textFieldEditingChanged:(UITextField *)textField {
|
||||
|
||||
if (textField == self.entryField) {
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone:
|
||||
break;
|
||||
case MPActiveUserStateLogin:
|
||||
break;
|
||||
case MPActiveUserStateUserName: {
|
||||
NSString *userName = self.entryField.text;
|
||||
[self selectedAvatar].name = [userName length]? userName: strl( @"New User" );
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
break;
|
||||
case MPActiveUserStateMasterPasswordConfirmation:
|
||||
break;
|
||||
case MPActiveUserStateMinimized:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
|
||||
if (collectionView == self.avatarCollectionView)
|
||||
return [self.userIDs count] + 1;
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
MPAvatarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[MPAvatarCell reuseIdentifier] forIndexPath:indexPath];
|
||||
[cell addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPress:)]];
|
||||
[self updateVisibilityForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
[self updateModeForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]
|
||||
isNew:&isNew];
|
||||
if (isNew) {
|
||||
// New User
|
||||
cell.avatar = MPAvatarAdd;
|
||||
cell.name = strl( @"New User" );
|
||||
}
|
||||
else {
|
||||
// Existing User
|
||||
cell.avatar = user.avatar;
|
||||
cell.name = user.name;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
Throw(@"unexpected collection view: %@", collectionView);
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
[self.avatarCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
|
||||
animated:YES];
|
||||
|
||||
// Deselect all other cells.
|
||||
for (NSUInteger otherItem = 0; otherItem < [collectionView numberOfItemsInSection:indexPath.section]; ++otherItem)
|
||||
if (otherItem != indexPath.item) {
|
||||
NSIndexPath *otherIndexPath = [NSIndexPath indexPathForItem:otherItem inSection:indexPath.section];
|
||||
[collectionView deselectItemAtIndexPath:otherIndexPath animated:YES];
|
||||
}
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForIndexPath:indexPath inContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]
|
||||
isNew:&isNew];
|
||||
if (isNew)
|
||||
self.activeUserState = MPActiveUserStateUserName;
|
||||
else if (!user.keyID)
|
||||
self.activeUserState = MPActiveUserStateMasterPasswordChoice;
|
||||
else
|
||||
self.activeUserState = MPActiveUserStateLogin;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (collectionView == self.avatarCollectionView) {
|
||||
self.activeUserState = MPActiveUserStateNone;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UILongPressGestureRecognizer
|
||||
|
||||
- (void)didLongPress:(UILongPressGestureRecognizer *)recognizer {
|
||||
|
||||
if ([recognizer.view isKindOfClass:[MPAvatarCell class]]) {
|
||||
if (recognizer.state != UIGestureRecognizerStateBegan)
|
||||
// Don't show the action menu unless the state is Began.
|
||||
return;
|
||||
|
||||
MPAvatarCell *avatarCell = (MPAvatarCell *)recognizer.view;
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
|
||||
BOOL isNew = NO;
|
||||
MPUserEntity *user = [self userForAvatar:avatarCell inContext:mainContext isNew:&isNew];
|
||||
NSManagedObjectID *userID = user.objectID;
|
||||
if (isNew || !user)
|
||||
return;
|
||||
|
||||
[PearlSheet showSheetWithTitle:user.name
|
||||
viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
if (buttonIndex == [sheet destructiveButtonIndex]) {
|
||||
// Delete User
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSManagedObject *user_ = [context existingObjectWithID:userID error:NULL];
|
||||
if (user_) {
|
||||
[context deleteObject:user_];
|
||||
[context saveToStore];
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonIndex == [sheet firstOtherButtonIndex])
|
||||
// Reset Password
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user_ = (MPUserEntity *)[context existingObjectWithID:userID error:NULL];
|
||||
if (user_)
|
||||
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
|
||||
dbg(@"changing mp for user: %@, keyID: %@", user_.name, user_.keyID);
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
|
||||
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
|
||||
scrollPosition:UICollectionViewScrollPositionNone];
|
||||
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel
|
||||
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity
|
||||
targetContentOffset:(inout CGPoint *)targetContentOffset {
|
||||
|
||||
if (scrollView == self.avatarCollectionView) {
|
||||
CGPoint offsetToCenter = CGPointMake(
|
||||
self.avatarCollectionView.bounds.size.width / 2,
|
||||
self.avatarCollectionView.bounds.size.height / 2 );
|
||||
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForItemAtPoint:
|
||||
CGPointPlusCGPoint( *targetContentOffset, offsetToCenter )];
|
||||
CGPoint targetCenter = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:avatarIndexPath].center;
|
||||
*targetContentOffset = CGPointMinusCGPoint( targetCenter, offsetToCenter );
|
||||
NSAssert([self.avatarCollectionView indexPathForItemAtPoint:targetCenter].item == avatarIndexPath.item, @"should be same item");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)firedMarqueeTimer:(NSTimer *)timer {
|
||||
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
self.hintLabel.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
if (!finished)
|
||||
return;
|
||||
|
||||
self.hintLabel.text = self.marqueeTipTexts[++self.marqueeTipTextIndex % [self.marqueeTipTexts count]];
|
||||
[UIView animateWithDuration:0.5 animations:^{
|
||||
self.hintLabel.alpha = 1;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (MPAvatarCell *)selectedAvatar {
|
||||
|
||||
NSArray *selectedIndexPaths = self.avatarCollectionView.indexPathsForSelectedItems;
|
||||
if (![selectedIndexPaths count]) {
|
||||
// No selected user.
|
||||
return nil;
|
||||
}
|
||||
|
||||
return (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:selectedIndexPaths.firstObject];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)selectedUserInContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
MPAvatarCell *selectedAvatar = [self selectedAvatar];
|
||||
if (!selectedAvatar) {
|
||||
// No selected user.
|
||||
*isNew = NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self userForAvatar:selectedAvatar inContext:context isNew:isNew];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForAvatar:(MPAvatarCell *)cell inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
return [self userForIndexPath:[self.avatarCollectionView indexPathForCell:cell] inContext:context isNew:isNew];
|
||||
}
|
||||
|
||||
- (MPUserEntity *)userForIndexPath:(NSIndexPath *)indexPath inContext:(NSManagedObjectContext *)context isNew:(BOOL *)isNew {
|
||||
|
||||
if ((*isNew = indexPath.item >= [self.userIDs count]))
|
||||
return nil;
|
||||
|
||||
NSError *error = nil;
|
||||
MPUserEntity *user = (MPUserEntity *)[context existingObjectWithID:self.userIDs[indexPath.item] error:&error];
|
||||
if (error)
|
||||
wrn(@"Failed to load user into context: %@", error);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
- (void)updateAvatars {
|
||||
|
||||
for (NSIndexPath *indexPath in self.avatarCollectionView.indexPathsForVisibleItems)
|
||||
[self updateAvatarAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (void)updateAvatarAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
MPAvatarCell *cell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
|
||||
[self updateVisibilityForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
[self updateModeForAvatar:cell atIndexPath:indexPath animated:NO];
|
||||
}
|
||||
|
||||
- (void)updateVisibilityForAvatar:(MPAvatarCell *)cell atIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
|
||||
|
||||
CGFloat current = [self.avatarCollectionView layoutAttributesForItemAtIndexPath:indexPath].center.x -
|
||||
self.avatarCollectionView.contentOffset.x;
|
||||
CGFloat max = self.avatarCollectionView.bounds.size.width;
|
||||
|
||||
[cell setVisibility:MAX(0, MIN( 1, 1 - ABS( current / (max / 2) - 1 ) )) animated:animated];
|
||||
}
|
||||
|
||||
- (void)updateModeForAvatar:(MPAvatarCell *)avatarCell atIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
|
||||
|
||||
switch (self.activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
[self.avatarCollectionView deselectItemAtIndexPath:indexPath animated:YES];
|
||||
[avatarCell setMode:MPAvatarModeLowered animated:animated];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin:
|
||||
case MPActiveUserStateUserName:
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
if ([self.avatarCollectionView.indexPathsForSelectedItems containsObject:indexPath])
|
||||
[avatarCell setMode:MPAvatarModeRaisedAndActive animated:animated];
|
||||
else
|
||||
[avatarCell setMode:MPAvatarModeRaisedButInactive animated:animated];
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized: {
|
||||
if ([self.avatarCollectionView.indexPathsForSelectedItems containsObject:indexPath])
|
||||
[avatarCell setMode:MPAvatarModeRaisedAndMinimized animated:animated];
|
||||
else
|
||||
[avatarCell setMode:MPAvatarModeRaisedAndHidden animated:animated];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
if ([_notificationObservers count])
|
||||
return;
|
||||
|
||||
Weakify(self);
|
||||
_notificationObservers = @[
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
// [self emergencyCloseAnimated:NO];
|
||||
self.userSelectionContainer.alpha = 0;
|
||||
}],
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
|
||||
[self reloadUsers];
|
||||
|
||||
[UIView animateWithDuration:1 animations:^{
|
||||
self.userSelectionContainer.alpha = 1;
|
||||
}];
|
||||
}],
|
||||
];
|
||||
|
||||
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
||||
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
|
||||
[_self updateAvatars];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
|
||||
for (id observer in _notificationObservers)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
_notificationObservers = nil;
|
||||
|
||||
[self removeKeyPathObservers];
|
||||
}
|
||||
|
||||
- (void)observeStore {
|
||||
|
||||
Weakify(self);
|
||||
|
||||
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!_mocObserver && mainContext)
|
||||
_mocObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
[self reloadUsers];
|
||||
}];
|
||||
if (!_storeObserver)
|
||||
_storeObserver = [[NSNotificationCenter defaultCenter]
|
||||
addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
Strongify(self);
|
||||
[self reloadUsers];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopObservingStore {
|
||||
|
||||
if (_mocObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
|
||||
if (_storeObserver)
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
|
||||
}
|
||||
|
||||
- (void)reloadUsers {
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
|
||||
if (!users) {
|
||||
err(@"Failed to load users: %@", error);
|
||||
self.userIDs = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *userIDs = [NSMutableArray arrayWithCapacity:[users count]];
|
||||
for (MPUserEntity *user in users)
|
||||
[userIDs addObject:user.objectID];
|
||||
self.userIDs = userIDs;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setActive:(BOOL)active {
|
||||
|
||||
[self setActive:active animated:YES];
|
||||
}
|
||||
|
||||
- (void)setActive:(BOOL)active animated:(BOOL)animated {
|
||||
|
||||
_active = active;
|
||||
dbg(@"active -> %d", active);
|
||||
|
||||
if (active)
|
||||
[self setActiveUserState:MPActiveUserStateNone animated:animated];
|
||||
else
|
||||
[self setActiveUserState:MPActiveUserStateMinimized animated:animated];
|
||||
}
|
||||
|
||||
- (void)setUserIDs:(NSArray *)userIDs {
|
||||
|
||||
_userIDs = userIDs;
|
||||
dbg(@"userIDs -> %lu", (unsigned long)[userIDs count]);
|
||||
|
||||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||||
[self.avatarCollectionView reloadData];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.userSelectionContainer.alpha = 1;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setActiveUserState:(MPActiveUserState)activeUserState {
|
||||
|
||||
[self setActiveUserState:activeUserState animated:YES];
|
||||
}
|
||||
|
||||
- (void)setActiveUserState:(MPActiveUserState)activeUserState animated:(BOOL)animated {
|
||||
|
||||
_activeUserState = activeUserState;
|
||||
_masterPasswordChoice = nil;
|
||||
|
||||
if (activeUserState != MPActiveUserStateMinimized && [MPiOSAppDelegate get].key) {
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
MPAvatarCell *selectedAvatar = [self selectedAvatar];
|
||||
|
||||
// Set avatar modes.
|
||||
for (NSUInteger item = 0; item < [self.avatarCollectionView numberOfItemsInSection:0]; ++item) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:0];
|
||||
MPAvatarCell *avatarCell = (MPAvatarCell *)[self.avatarCollectionView cellForItemAtIndexPath:indexPath];
|
||||
[self updateModeForAvatar:avatarCell atIndexPath:indexPath animated:NO];
|
||||
|
||||
if (selectedAvatar && avatarCell == selectedAvatar)
|
||||
[self.avatarCollectionView scrollToItemAtIndexPath:indexPath
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
|
||||
}
|
||||
|
||||
// Set the entry container's contents.
|
||||
switch (activeUserState) {
|
||||
case MPActiveUserStateNone:
|
||||
dbg(@"activeUserState -> none");
|
||||
break;
|
||||
case MPActiveUserStateLogin: {
|
||||
dbg(@"activeUserState -> login");
|
||||
self.entryLabel.text = strl( @"Enter your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateUserName: {
|
||||
dbg(@"activeUserState -> userName");
|
||||
self.entryLabel.text = strl( @"Enter your full name:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = NO;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordChoice: {
|
||||
dbg(@"activeUserState -> masterPasswordChoice");
|
||||
self.entryLabel.text = strl( @"Choose your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
dbg(@"activeUserState -> masterPasswordConfirmation");
|
||||
_masterPasswordChoice = self.entryField.text;
|
||||
self.entryLabel.text = strl( @"Confirm your master password:" );
|
||||
self.entryField.text = nil;
|
||||
self.entryField.secureTextEntry = YES;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized:
|
||||
dbg(@"activeUserState -> minimized");
|
||||
break;
|
||||
}
|
||||
|
||||
// Manage the random avatar for the new user if selected.
|
||||
if (selectedAvatar.avatar == MPAvatarAdd)
|
||||
selectedAvatar.avatar = arc4random() % MPAvatarCount;
|
||||
else {
|
||||
NSIndexPath *newUserIndexPath = [NSIndexPath indexPathForItem:[_userIDs count] inSection:0];
|
||||
MPAvatarCell *newUserAvatar = (MPAvatarCell *)[[self avatarCollectionView] cellForItemAtIndexPath:newUserIndexPath];
|
||||
newUserAvatar.avatar = MPAvatarAdd;
|
||||
newUserAvatar.name = strl( @"New User" );
|
||||
}
|
||||
|
||||
// Manage the entry container depending on whether a user is activate or not.
|
||||
switch (activeUserState) {
|
||||
case MPActiveUserStateNone: {
|
||||
self.navigationBarToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarCollectionView.scrollEnabled = YES;
|
||||
self.entryContainer.alpha = 0;
|
||||
self.footerContainer.alpha = 1;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateLogin:
|
||||
case MPActiveUserStateUserName:
|
||||
case MPActiveUserStateMasterPasswordChoice:
|
||||
case MPActiveUserStateMasterPasswordConfirmation: {
|
||||
self.navigationBarToTopConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.avatarCollectionView.scrollEnabled = NO;
|
||||
self.entryContainer.alpha = 1;
|
||||
self.footerContainer.alpha = 1;
|
||||
break;
|
||||
}
|
||||
case MPActiveUserStateMinimized: {
|
||||
self.navigationBarToTopConstraint.priority = 1;
|
||||
self.avatarCollectionCenterConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
self.avatarCollectionView.scrollEnabled = NO;
|
||||
self.entryContainer.alpha = 0;
|
||||
self.footerContainer.alpha = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
[self.navigationBarToTopConstraint apply];
|
||||
[self.avatarCollectionCenterConstraint apply];
|
||||
|
||||
// Toggle the keyboard.
|
||||
if (!self.entryContainer.alpha)
|
||||
[self.entryField resignFirstResponder];
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished && self.entryContainer.alpha)
|
||||
[self.entryField becomeFirstResponder];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)doSignOut:(UIBarButtonItem *)sender {
|
||||
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
}
|
||||
|
||||
@end
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="4514" systemVersion="13C64" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="5053" systemVersion="13C64" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1536" identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Type View Controller - Type-->
|
||||
@ -1198,6 +1198,7 @@ L4m3P4sSw0rD</string>
|
||||
<navigationItem key="navigationItem" id="zZZ-QZ-Yur"/>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="305" height="402"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="hkm-U7-Dm7" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
@ -2546,6 +2547,7 @@ Only site names and custom passwords are sent to iCloud. Passwords are encrypte
|
||||
<navigationItem key="navigationItem" id="Pm8-fx-hfM"/>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="305" height="402"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="LHv-Mk-8Kp" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
|
@ -12,7 +12,10 @@
|
||||
93D39262A8A97DB748213309 /* PearlEMail.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393BB973253D4BAAC84AA /* PearlEMail.m */; };
|
||||
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
|
||||
93D3932889B6B4206E66A6D6 /* PearlEMail.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */; };
|
||||
93D393543ACC701C018C74DA /* PearlUIView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393676C32D23A47E27957 /* PearlUIView.m */; };
|
||||
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; };
|
||||
93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; };
|
||||
93D3959643EACF286D0152BA /* PearlUINavigationBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DDDAC305E8ABB4220C7 /* PearlUINavigationBar.m */; };
|
||||
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
|
||||
93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3956915634581E737B38C /* PearlNavigationController.m */; };
|
||||
93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D3942A356B639724157982 /* PearlOverlay.h */; };
|
||||
@ -21,6 +24,7 @@
|
||||
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
|
||||
93D399BBC0A7EC746CB1B19B /* MPLogsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D391943675426839501BB8 /* MPLogsViewController.h */; };
|
||||
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; };
|
||||
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; };
|
||||
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
||||
93D39C8AD8EAB747856B3A8C /* LLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3923B42DA2DA18F287092 /* LLModel.m */; };
|
||||
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
|
||||
@ -496,22 +500,29 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
|
||||
93D39083C93D90C4B94541AD /* PearlUIView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUIView.h; sourceTree = "<group>"; };
|
||||
93D390A66F69AB1CDB0BFF93 /* LLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLModel.h; sourceTree = "<group>"; };
|
||||
93D390EEC85E94D9C888643F /* PearlUINavigationBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUINavigationBar.h; sourceTree = "<group>"; };
|
||||
93D390FADEB325D8D54A957D /* PearlOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlOverlay.m; sourceTree = "<group>"; };
|
||||
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsViewController.h; sourceTree = "<group>"; };
|
||||
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
|
||||
93D3923B42DA2DA18F287092 /* LLModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLModel.m; sourceTree = "<group>"; };
|
||||
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
|
||||
93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; };
|
||||
93D393676C32D23A47E27957 /* PearlUIView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUIView.m; sourceTree = "<group>"; };
|
||||
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
|
||||
93D393BB973253D4BAAC84AA /* PearlEMail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlEMail.m; sourceTree = "<group>"; };
|
||||
93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; };
|
||||
93D3942A356B639724157982 /* PearlOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlOverlay.h; sourceTree = "<group>"; };
|
||||
93D3956915634581E737B38C /* PearlNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlNavigationController.m; sourceTree = "<group>"; };
|
||||
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
|
||||
93D3971FE104BB4052484151 /* MPUsersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUsersViewController.h; sourceTree = "<group>"; };
|
||||
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSetupViewController.h; sourceTree = "<group>"; };
|
||||
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogsViewController.m; sourceTree = "<group>"; };
|
||||
93D3983278751A530262F64E /* LLConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLConfig.h; sourceTree = "<group>"; };
|
||||
93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = "<group>"; };
|
||||
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
|
||||
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = "<group>"; };
|
||||
93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = "<group>"; };
|
||||
93D39A3CC4D8330831FC8CB4 /* LLToggleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLToggleViewController.h; sourceTree = "<group>"; };
|
||||
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
|
||||
@ -521,6 +532,7 @@
|
||||
93D39C8E26B06F01566785B7 /* LLToggleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLToggleViewController.m; sourceTree = "<group>"; };
|
||||
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCombinedViewController.h; sourceTree = "<group>"; };
|
||||
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAvatarCell.h; sourceTree = "<group>"; };
|
||||
93D39DDDAC305E8ABB4220C7 /* PearlUINavigationBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUINavigationBar.m; sourceTree = "<group>"; };
|
||||
93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = "<group>"; };
|
||||
93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
|
||||
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
|
||||
@ -1666,6 +1678,10 @@
|
||||
DAFC5657172C573B00CB5CC5 /* InAppSettingsKit */,
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA45147E415C00F98B1E /* Products */,
|
||||
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */,
|
||||
93D3971FE104BB4052484151 /* MPUsersViewController.h */,
|
||||
93D393676C32D23A47E27957 /* PearlUIView.m */,
|
||||
93D39083C93D90C4B94541AD /* PearlUIView.h */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -2514,11 +2530,15 @@
|
||||
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */,
|
||||
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */,
|
||||
93D391943675426839501BB8 /* MPLogsViewController.h */,
|
||||
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */,
|
||||
93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */,
|
||||
93D393310223DDB35218467A /* MPCombinedViewController.m */,
|
||||
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */,
|
||||
DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */,
|
||||
93D39B381350802A194BF332 /* MPAvatarCell.m */,
|
||||
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */,
|
||||
93D39DDDAC305E8ABB4220C7 /* PearlUINavigationBar.m */,
|
||||
93D390EEC85E94D9C888643F /* PearlUINavigationBar.h */,
|
||||
);
|
||||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
@ -3819,6 +3839,10 @@
|
||||
DA095E75172F4CD8001C948B /* MPLogsViewController.m in Sources */,
|
||||
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */,
|
||||
93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */,
|
||||
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */,
|
||||
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */,
|
||||
93D3959643EACF286D0152BA /* PearlUINavigationBar.m in Sources */,
|
||||
93D393543ACC701C018C74DA /* PearlUIView.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
26
MasterPassword/ObjC/iOS/PearlUINavigationBar.h
Normal file
26
MasterPassword/ObjC/iOS/PearlUINavigationBar.h
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//
|
||||
// PearlUINavigationBar.h
|
||||
// PearlUINavigationBar
|
||||
//
|
||||
// Created by lhunath on 2014-03-17.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface PearlUINavigationBar : UINavigationBar
|
||||
|
||||
@property (assign, nonatomic) BOOL ignoreTouches;
|
||||
@property (assign, nonatomic) BOOL invisible;
|
||||
|
||||
@end
|
43
MasterPassword/ObjC/iOS/PearlUINavigationBar.m
Normal file
43
MasterPassword/ObjC/iOS/PearlUINavigationBar.m
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//
|
||||
// PearlUINavigationBar.h
|
||||
// PearlUINavigationBar
|
||||
//
|
||||
// Created by lhunath on 2014-03-17.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PearlUINavigationBar.h"
|
||||
|
||||
@implementation PearlUINavigationBar
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
|
||||
UIView *hitView = [super hitTest:point withEvent:event];
|
||||
if (self.ignoreTouches && hitView == self)
|
||||
return nil;
|
||||
|
||||
return hitView;
|
||||
}
|
||||
|
||||
- (void)setInvisible:(BOOL)invisible {
|
||||
|
||||
_invisible = invisible;
|
||||
|
||||
if (invisible) {
|
||||
self.translucent = YES;
|
||||
self.shadowImage = [UIImage new];
|
||||
[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
25
MasterPassword/ObjC/iOS/PearlUIView.h
Normal file
25
MasterPassword/ObjC/iOS/PearlUIView.h
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// PearlUIView.h
|
||||
// PearlUIView
|
||||
//
|
||||
// Created by lhunath on 2014-03-17.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface PearlUIView : UIView
|
||||
|
||||
@property(assign, nonatomic) BOOL ignoreTouches;
|
||||
|
||||
@end
|
32
MasterPassword/ObjC/iOS/PearlUIView.m
Normal file
32
MasterPassword/ObjC/iOS/PearlUIView.m
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
//
|
||||
// PearlUIView.h
|
||||
// PearlUIView
|
||||
//
|
||||
// Created by lhunath on 2014-03-17.
|
||||
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PearlUIView.h"
|
||||
|
||||
@implementation PearlUIView
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
|
||||
UIView *hitView = [super hitTest:point withEvent:event];
|
||||
if (self.ignoreTouches && hitView == self)
|
||||
return nil;
|
||||
|
||||
return hitView;
|
||||
}
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user