303 lines
13 KiB
Objective-C
303 lines
13 KiB
Objective-C
//==============================================================================
|
|
// This file is part of Master Password.
|
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
//
|
|
// Master Password is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Master Password is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You can find a copy of the GNU General Public License in the
|
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
//==============================================================================
|
|
|
|
#import "MPAvatarCell.h"
|
|
|
|
const long MPAvatarAdd = 10000;
|
|
|
|
@interface MPAvatarCell()
|
|
|
|
@property(strong, nonatomic) IBOutlet UIImageView *avatarImageView;
|
|
@property(strong, nonatomic) IBOutlet UILabel *nameLabel;
|
|
@property(strong, nonatomic) IBOutlet UIView *nameContainer;
|
|
@property(strong, nonatomic) IBOutlet UIImageView *spinner;
|
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *nameToCenterConstraint;
|
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarSizeConstraint;
|
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarToTopConstraint;
|
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *avatarRaisedConstraint;
|
|
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *keyboardHeightConstraint;
|
|
|
|
@property(nonatomic, strong) CAAnimationGroup *targetedShadowAnimation;
|
|
|
|
@property(assign, nonatomic, readwrite) BOOL newUser;
|
|
@end
|
|
|
|
@implementation MPAvatarCell
|
|
|
|
+ (NSString *)reuseIdentifier {
|
|
|
|
return NSStringFromClass( self );
|
|
}
|
|
|
|
#pragma mark - Life cycle
|
|
|
|
- (void)awakeFromNib {
|
|
|
|
[super awakeFromNib];
|
|
|
|
self.visible = NO;
|
|
|
|
self.nameContainer.layer.cornerRadius = 5;
|
|
|
|
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
|
|
self.avatarImageView.layer.masksToBounds = NO;
|
|
self.avatarImageView.backgroundColor = [UIColor clearColor];
|
|
|
|
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;
|
|
|
|
self.targetedShadowAnimation = [CAAnimationGroup new];
|
|
self.targetedShadowAnimation.animations = @[ toShadowOpacityAnimation, pulseShadowOpacityAnimation ];
|
|
self.targetedShadowAnimation.duration = MAXFLOAT;
|
|
self.avatarImageView.layer.shadowColor = [UIColor whiteColor].CGColor;
|
|
self.avatarImageView.layer.shadowOffset = CGSizeZero;
|
|
|
|
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
|
|
self.contentView.frame = self.bounds;
|
|
}];
|
|
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
|
|
[self updateAnimated:self.superview != nil];
|
|
}];
|
|
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
|
|
[self updateAnimated:self.superview != nil];
|
|
}];
|
|
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
|
|
^(MPAvatarCell *self, NSNotification *note) {
|
|
CGRect keyboardRect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
|
CGFloat keyboardHeight = CGRectGetHeight( self.window.screen.bounds ) - CGRectGetMinY( keyboardRect );
|
|
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
|
|
} );
|
|
}
|
|
|
|
- (void)prepareForReuse {
|
|
|
|
[super prepareForReuse];
|
|
|
|
self.newUser = NO;
|
|
self.visibility = 0;
|
|
self.mode = MPAvatarModeLowered;
|
|
self.spinnerActive = NO;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
|
|
[self removeKeyPathObservers];
|
|
PearlRemoveNotificationObservers();
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (void)setAvatar:(NSUInteger)avatar {
|
|
|
|
_avatar = avatar == MPAvatarAdd? MPAvatarAdd: (avatar + MPAvatarCount) % MPAvatarCount;
|
|
|
|
if (self.avatar == MPAvatarAdd) {
|
|
self.avatarImageView.image = [UIImage imageNamed:@"avatar-add"];
|
|
self.name = strl( @"New User" );
|
|
self.newUser = YES;
|
|
}
|
|
else
|
|
self.avatarImageView.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)self.avatar )];
|
|
}
|
|
|
|
- (NSString *)name {
|
|
|
|
return self.nameLabel.text;
|
|
}
|
|
|
|
- (void)setName:(NSString *)name {
|
|
|
|
self.nameLabel.text = name;
|
|
}
|
|
|
|
- (void)setVisibility:(CGFloat)visibility {
|
|
|
|
[self setVisibility:visibility animated:YES];
|
|
}
|
|
|
|
- (void)setVisibility:(CGFloat)visibility animated:(BOOL)animated {
|
|
|
|
if (self.visibility == visibility)
|
|
return;
|
|
_visibility = visibility;
|
|
|
|
[self updateAnimated:animated];
|
|
}
|
|
|
|
- (void)setHighlighted:(BOOL)highlighted {
|
|
|
|
super.highlighted = highlighted;
|
|
|
|
self.avatarImageView.transform = highlighted? CGAffineTransformMakeScale( 1.1f, 1.1f ): CGAffineTransformIdentity;
|
|
}
|
|
|
|
- (void)setMode:(MPAvatarMode)mode {
|
|
|
|
[self setMode:mode animated:YES];
|
|
}
|
|
|
|
- (void)setMode:(MPAvatarMode)mode animated:(BOOL)animated {
|
|
|
|
if (self.mode == mode)
|
|
return;
|
|
_mode = mode;
|
|
|
|
[self updateAnimated:animated];
|
|
}
|
|
|
|
- (void)setSpinnerActive:(BOOL)spinnerActive {
|
|
|
|
[self setSpinnerActive:spinnerActive animated:YES];
|
|
}
|
|
|
|
- (void)setSpinnerActive:(BOOL)spinnerActive animated:(BOOL)animated {
|
|
|
|
if (self.spinnerActive == spinnerActive)
|
|
return;
|
|
_spinnerActive = spinnerActive;
|
|
|
|
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
|
|
rotate.toValue = @(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 {
|
|
|
|
[self.contentView layoutIfNeeded];
|
|
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
|
|
self.avatarImageView.transform = CGAffineTransformIdentity;
|
|
}];
|
|
[UIView animateWithDuration:animated? 0.5f: 0 delay:0
|
|
options:UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionBeginFromCurrentState
|
|
animations:^{
|
|
self.visible = YES;
|
|
|
|
if (self.newUser) {
|
|
if (self.mode == MPAvatarModeLowered)
|
|
self.avatar = MPAvatarAdd;
|
|
else if (self.avatar == MPAvatarAdd)
|
|
self.avatar = arc4random() % MPAvatarCount;
|
|
}
|
|
|
|
self.nameContainer.alpha = self.visibility;
|
|
self.avatarImageView.alpha = self.visibility * 0.7f + 0.3f;
|
|
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
|
|
|
|
switch (self.mode) {
|
|
case MPAvatarModeLowered: {
|
|
[self.avatarSizeConstraint updateConstant:
|
|
self.avatarImageView.image.size.height * (self.visibility * 0.3f + 0.7f)];
|
|
[self.avatarRaisedConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
[self.avatarToTopConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
[self.nameToCenterConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
self.nameContainer.visible = YES;
|
|
self.nameContainer.backgroundColor = [UIColor clearColor];
|
|
self.avatarImageView.visible = YES;
|
|
break;
|
|
}
|
|
case MPAvatarModeRaisedButInactive: {
|
|
[self.avatarSizeConstraint updateConstant:
|
|
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
|
|
[self.avatarRaisedConstraint withPriority:UILayoutPriorityDefaultHigh];
|
|
[self.avatarToTopConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
[self.nameToCenterConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
self.nameContainer.visible = YES;
|
|
self.nameContainer.backgroundColor = [UIColor clearColor];
|
|
self.avatarImageView.visible = NO;
|
|
break;
|
|
}
|
|
case MPAvatarModeRaisedAndActive: {
|
|
[self.avatarSizeConstraint updateConstant:
|
|
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
|
|
[self.avatarRaisedConstraint withPriority:UILayoutPriorityDefaultHigh];
|
|
[self.avatarToTopConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
[self.nameToCenterConstraint withPriority:UILayoutPriorityDefaultHigh];
|
|
self.nameContainer.visible = YES;
|
|
self.nameContainer.backgroundColor = [UIColor blackColor];
|
|
self.avatarImageView.visible = YES;
|
|
break;
|
|
}
|
|
case MPAvatarModeRaisedAndHidden: {
|
|
[self.avatarSizeConstraint updateConstant:
|
|
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
|
|
[self.avatarRaisedConstraint withPriority:UILayoutPriorityDefaultHigh];
|
|
[self.avatarToTopConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
[self.nameToCenterConstraint withPriority:UILayoutPriorityDefaultHigh];
|
|
self.nameContainer.visible = NO;
|
|
self.nameContainer.backgroundColor = [UIColor blackColor];
|
|
self.avatarImageView.visible = NO;
|
|
break;
|
|
}
|
|
case MPAvatarModeRaisedAndMinimized: {
|
|
[self.avatarSizeConstraint updateConstant:36];
|
|
[self.avatarRaisedConstraint withPriority:UILayoutPriorityDefaultLow];
|
|
[self.avatarToTopConstraint withPriority:UILayoutPriorityDefaultHigh + 2];
|
|
[self.nameToCenterConstraint withPriority:UILayoutPriorityDefaultHigh];
|
|
self.nameContainer.visible = NO;
|
|
self.nameContainer.backgroundColor = [UIColor blackColor];
|
|
self.avatarImageView.visible = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Avatar minimized.
|
|
if (self.mode == MPAvatarModeRaisedAndMinimized)
|
|
[self.avatarImageView.layer removeAnimationForKey:@"targetedShadow"];
|
|
else if (![self.avatarImageView.layer animationForKey:@"targetedShadow"])
|
|
[self.avatarImageView.layer addAnimation:self.targetedShadowAnimation forKey:@"targetedShadow"];
|
|
|
|
// Avatar selection and spinner.
|
|
if (self.mode != MPAvatarModeRaisedAndMinimized && (self.selected || self.highlighted) && !self.spinnerActive)
|
|
self.avatarImageView.backgroundColor = self.avatarImageView.tintColor;
|
|
else
|
|
self.avatarImageView.backgroundColor = [UIColor clearColor];
|
|
self.avatarImageView.layer.cornerRadius = self.avatarImageView.bounds.size.height / 2;
|
|
self.spinner.visible = self.spinnerActive;
|
|
|
|
[self.contentView layoutIfNeeded];
|
|
} completion:nil];
|
|
}
|
|
|
|
@end
|