client/iOS/Helper/PSStoreButton.m
//
//  PSStoreButton.m
//  HockeyDemo
//
//  Created by Peter Steinberger on 09.01.11.
//  Copyright 2011 Peter Steinberger. All rights reserved.
//
//  This code was inspired by https://github.com/dhmspector/ZIStoreButton
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

#import "PSStoreButton.h"

#ifdef DEBUG
#define PSLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define PSLog(...)
#endif

#define PS_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define PS_MIN_HEIGHT 25.0f
#define PS_MAX_WIDTH 120.0f
#define PS_PADDING 12.0f
#define kDefaultButtonAnimationTime 0.25f

@implementation PSStoreButtonData

@synthesize label = label_;
@synthesize colors = colors_;
@synthesize enabled = enabled_;

///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject

- (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag {
  if ((self = [super init])) {
    self.label = aLabel;
    self.colors = aColors;
    self.enabled = flag;
  }
  return self;
}

+ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag {
  return [[[[self class] alloc] initWithLabel:aLabel colors:aColors enabled:flag] autorelease];
}

- (void)dealloc {
  [label_ release];
  [colors_ release];
  
  [super dealloc];
}
@end


@interface PSStoreButton ()
// call when buttonData was updated
- (void)updateButtonAnimated:(BOOL)animated;
@end


@implementation PSStoreButton

@synthesize buttonData = buttonData_;
@synthesize buttonDelegate = buttonDelegate_;
@synthesize customPadding = customPadding_;

///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private

- (void)touchedUpOutside:(id)sender {
  PSLog(@"touched outside...");
}

- (void)buttonPressed:(id)sender {
  PSLog(@"calling delegate:storeButtonFired for %@", sender);
  [buttonDelegate_ storeButtonFired:self];
}

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
  // show text again, but only if animation did finish (or else another animation is on the way)
  if ([finished boolValue]) {
    [self setTitle:self.buttonData.label forState:UIControlStateNormal];
  }
}

- (void)updateButtonAnimated:(BOOL)animated {
  if (animated) {
    // hide text, then start animation
    [self setTitle:@"" forState:UIControlStateNormal];
    [UIView beginAnimations:@"storeButtonUpdate" context:nil];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:kDefaultButtonAnimationTime];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
  }else {
    [self setTitle:self.buttonData.label forState:UIControlStateNormal];
  }
  
  self.enabled = self.buttonData.isEnabled;
  gradient_.colors = self.buttonData.colors;
  
  // show white or gray text, depending on the state
  if (self.buttonData.isEnabled) {
    [self setTitleShadowColor:[UIColor colorWithWhite:0.200 alpha:1.000] forState:UIControlStateNormal];
    [self.titleLabel setShadowOffset:CGSizeMake(0.0, -0.6)];
    [self setTitleColor:[UIColor colorWithWhite:1.0 alpha:1.000] forState:UIControlStateNormal];
  }else {
    [self.titleLabel setShadowOffset:CGSizeMake(0.0, 0.0)];
    [self setTitleColor:PS_RGBCOLOR(148,150,151) forState:UIControlStateNormal];
  }
  
  // calculate optimal new size
  CGSize sizeThatFits = [self sizeThatFits:CGSizeZero];
  
  // move sublayer (can't be animated explcitely)
  for (CALayer *aLayer in self.layer.sublayers) {
    [CATransaction begin];
    
    if (animated) {
      [CATransaction setAnimationDuration:kDefaultButtonAnimationTime];
      [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    }else {
      // frame is calculated and explicitely animated. so we absolutely need kCATransactionDisableActions
      [CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions];
    }
    
    CGRect newFrame = aLayer.frame;
    newFrame.size.width = sizeThatFits.width;
    aLayer.frame = newFrame;
    
    [CATransaction commit];
	}
  
  // set outer frame changes
  self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0);
  [self alignToSuperview];
  
  if (animated) {
    [UIView commitAnimations];
  }
}

- (void)alignToSuperview {
  [self sizeToFit];
  if (self.superview) {
    CGRect cr = self.frame;
    cr.origin.y = customPadding_.y;
    cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2;
    self.frame = cr;
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject

- (id)initWithFrame:(CGRect)frame {
  if ((self = [super initWithFrame:frame])) {
		self.layer.needsDisplayOnBoundsChange = YES;
    
    // setup title label
    [self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]];
    
    // register for touch events
    [self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
		[self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
    
    // border layers for more sex!
    CAGradientLayer *bevelLayer = [CAGradientLayer layer];
		bevelLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.4 alpha:1.0] CGColor], [[UIColor whiteColor] CGColor], nil];
		bevelLayer.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame));
		bevelLayer.cornerRadius = 2.5;
		bevelLayer.needsDisplayOnBoundsChange = YES;
    [self.layer addSublayer:bevelLayer];
    
		CAGradientLayer *topBorderLayer = [CAGradientLayer layer];
		topBorderLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor darkGrayColor] CGColor], [[UIColor lightGrayColor] CGColor], nil];
		topBorderLayer.frame = CGRectMake(0.5, 0.5, CGRectGetWidth(frame) - 1.0, CGRectGetHeight(frame) - 1.0);
		topBorderLayer.cornerRadius = 2.6;
		topBorderLayer.needsDisplayOnBoundsChange = YES;
		[self.layer addSublayer:topBorderLayer];
    
    // main gradient layer
    gradient_ = [[CAGradientLayer layer] retain];
    gradient_.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:1.0], nil];//[NSNumber numberWithFloat:0.500], [NSNumber numberWithFloat:0.5001],
		gradient_.frame = CGRectMake(0.75, 0.75, CGRectGetWidth(frame) - 1.5, CGRectGetHeight(frame) - 1.5);
		gradient_.cornerRadius = 2.5;
		gradient_.needsDisplayOnBoundsChange = YES;
    [self.layer addSublayer:gradient_];
    [self bringSubviewToFront:self.titleLabel];
  }
  return self;
}

- (id)initWithPadding:(CGPoint)padding {
  if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) {
    customPadding_ = padding;
  }
  return self;
}

- (void)dealloc {
  [buttonData_ release];
  [gradient_ release];
  
  [super dealloc];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView

- (CGSize)sizeThatFits:(CGSize)size {
  CGSize constr = (CGSize){.height = self.frame.size.height, .width = PS_MAX_WIDTH};
	CGSize newSize = [self.buttonData.label sizeWithFont:self.titleLabel.font constrainedToSize:constr lineBreakMode:UILineBreakModeMiddleTruncation];
	CGFloat newWidth = newSize.width + (PS_PADDING * 2);
  CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height;
  
  CGSize sizeThatFits = CGSizeMake(newWidth, newHeight);
  return sizeThatFits;
}

- (void)setFrame:(CGRect)aRect {
  [super setFrame:aRect];
  
  // copy frame changes to sublayers (but watch out for NaN's)
  for (CALayer *aLayer in self.layer.sublayers) {
    CGRect rect = aLayer.frame;
    rect.size.width = self.frame.size.width;
    rect.size.height = self.frame.size.height;
    aLayer.frame = rect;
    [aLayer layoutIfNeeded];
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Properties

- (void)setButtonData:(PSStoreButtonData *)aButtonData {
  [self setButtonData:aButtonData animated:NO];
}

- (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated {
  if (buttonData_ != aButtonData) {
    [buttonData_ release];
    buttonData_ = [aButtonData retain];
  }
  
  [self updateButtonAnimated:animated];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Static

+ (NSArray *)appStoreGreenColor {
  return [NSArray arrayWithObjects:(id)
          [UIColor colorWithRed:0.482 green:0.674 blue:0.406 alpha:1.000].CGColor,
          [UIColor colorWithRed:0.299 green:0.606 blue:0.163 alpha:1.000].CGColor, nil];
}

+ (NSArray *)appStoreBlueColor {
  return [NSArray arrayWithObjects:(id)
          [UIColor colorWithRed:0.306 green:0.380 blue:0.547 alpha:1.000].CGColor,
          [UIColor colorWithRed:0.129 green:0.220 blue:0.452 alpha:1.000].CGColor, nil];
}

+ (NSArray *)appStoreGrayColor {
  return [NSArray arrayWithObjects:(id)
          PS_RGBCOLOR(187,189,191).CGColor,
          PS_RGBCOLOR(210,210,210).CGColor, nil];
}

@end
