1//
  2//  BlockAlertView.m
  3//
  4//
  5
  6#import "BlockAlertView.h"
  7#import "BlockBackground.h"
  8#import "BlockUI.h"
  9
 10@implementation BlockAlertView
 11
 12@synthesize view = _view;
 13@synthesize backgroundImage = _backgroundImage;
 14@synthesize vignetteBackground = _vignetteBackground;
 15
 16static UIImage *background = nil;
 17static UIImage *backgroundlandscape = nil;
 18static UIFont *titleFont = nil;
 19static UIFont *messageFont = nil;
 20static UIFont *buttonFont = nil;
 21
 22
 23#pragma mark - init
 24
 25+ (void)initialize
 26{
 27    if (self == [BlockAlertView class])
 28    {
 29        background = [UIImage imageNamed:kAlertViewBackground];
 30        background = [[background stretchableImageWithLeftCapWidth:0 topCapHeight:kAlertViewBackgroundCapHeight] retain];
 31        
 32        backgroundlandscape = [UIImage imageNamed:kAlertViewBackgroundLandscape];
 33        backgroundlandscape = [[backgroundlandscape stretchableImageWithLeftCapWidth:0 topCapHeight:kAlertViewBackgroundCapHeight] retain];
 34        
 35        titleFont = [kAlertViewTitleFont retain];
 36        messageFont = [kAlertViewMessageFont retain];
 37        buttonFont = [kAlertViewButtonFont retain];
 38    }
 39}
 40
 41+ (BlockAlertView *)alertWithTitle:(NSString *)title message:(NSString *)message
 42{
 43    return [[[BlockAlertView alloc] initWithTitle:title message:message] autorelease];
 44}
 45
 46+ (void)showInfoAlertWithTitle:(NSString *)title message:(NSString *)message
 47{
 48    BlockAlertView *alert = [[BlockAlertView alloc] initWithTitle:title message:message];
 49    [alert setCancelButtonWithTitle:NSLocalizedString(@"Dismiss", nil) block:nil];
 50    [alert show];
 51    [alert release];
 52}
 53
 54+ (void)showErrorAlert:(NSError *)error
 55{
 56    BlockAlertView *alert = [[BlockAlertView alloc] initWithTitle:NSLocalizedString(@"Operation Failed", nil) message:[NSString stringWithFormat:NSLocalizedString(@"The operation did not complete successfully: %@", nil), error]];
 57    [alert setCancelButtonWithTitle:@"Dismiss" block:nil];
 58    [alert show];
 59    [alert release];
 60}
 61
 62///////////////////////////////////////////////////////////////////////////////////////////////////
 63#pragma mark - NSObject
 64
 65- (void)addComponents:(CGRect)frame {
 66    if (_title)
 67    {
 68        CGSize size = [_title sizeWithFont:titleFont
 69                         constrainedToSize:CGSizeMake(frame.size.width-kAlertViewBorder*2, 1000)
 70                             lineBreakMode:NSLineBreakByWordWrapping];
 71        
 72        UILabel *labelView = [[UILabel alloc] initWithFrame:CGRectMake(kAlertViewBorder, _height, frame.size.width-kAlertViewBorder*2, size.height)];
 73        labelView.font = titleFont;
 74        labelView.numberOfLines = 0;
 75        labelView.lineBreakMode = NSLineBreakByWordWrapping;
 76        labelView.textColor = kAlertViewTitleTextColor;
 77        labelView.backgroundColor = [UIColor clearColor];
 78        labelView.textAlignment = NSTextAlignmentCenter;
 79        labelView.shadowColor = kAlertViewTitleShadowColor;
 80        labelView.shadowOffset = kAlertViewTitleShadowOffset;
 81        labelView.text = _title;
 82        [_view addSubview:labelView];
 83        [labelView release];
 84        
 85        _height += size.height + kAlertViewBorder;
 86    }
 87    
 88    if (_message)
 89    {
 90        CGSize size = [_message sizeWithFont:messageFont
 91                           constrainedToSize:CGSizeMake(frame.size.width-kAlertViewBorder*2, 1000)
 92                               lineBreakMode:NSLineBreakByWordWrapping];
 93        
 94        UILabel *labelView = [[UILabel alloc] initWithFrame:CGRectMake(kAlertViewBorder, _height, frame.size.width-kAlertViewBorder*2, size.height)];
 95        labelView.font = messageFont;
 96        labelView.numberOfLines = 0;
 97        labelView.lineBreakMode = NSLineBreakByWordWrapping;
 98        labelView.textColor = kAlertViewMessageTextColor;
 99        labelView.backgroundColor = [UIColor clearColor];
100        labelView.textAlignment = NSTextAlignmentCenter;
101        labelView.shadowColor = kAlertViewMessageShadowColor;
102        labelView.shadowOffset = kAlertViewMessageShadowOffset;
103        labelView.text = _message;
104        [_view addSubview:labelView];
105        [labelView release];
106        
107        _height += size.height + kAlertViewBorder;
108    }
109}
110
111- (void)setupDisplay
112{
113    [[_view subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
114        [obj removeFromSuperview];
115    }];
116    
117    UIWindow *parentView = [BlockBackground sharedInstance];
118    CGRect frame = parentView.bounds;
119    frame.origin.x = floorf((frame.size.width - background.size.width) * 0.5);
120    frame.size.width = background.size.width;
121    
122    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
123    if (UIInterfaceOrientationIsLandscape(orientation)) {
124        frame.size.width += 150;
125        frame.origin.x -= 75;
126    }
127    
128    _view.frame = frame;
129    
130    _height = kAlertViewBorder + 15;
131    
132    if (NeedsLandscapePhoneTweaks) {
133        _height -= 15; // landscape phones need to trimmed a bit
134    }
135
136    [self addComponents:frame];
137
138    if (_shown)
139        [self show];
140}
141
142- (id)initWithTitle:(NSString *)title message:(NSString *)message 
143{
144    self = [super init];
145    
146    if (self)
147    {
148        _title = [title copy];
149        _message = [message copy];
150        
151        _view = [[UIView alloc] init];
152        
153        _view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
154        
155        _blocks = [[NSMutableArray alloc] init];
156        
157        [[NSNotificationCenter defaultCenter] addObserver:self 
158                                                 selector:@selector(setupDisplay) 
159                                                     name:UIApplicationDidChangeStatusBarOrientationNotification 
160                                                   object:nil];   
161        
162        if ([self class] == [BlockAlertView class])
163            [self setupDisplay];
164        
165        _vignetteBackground = NO;
166    }
167    
168    return self;
169}
170
171- (void)dealloc 
172{
173    [_title release];
174    [_message release];
175    [_backgroundImage release];
176    [_view release];
177    [_blocks release];
178    [super dealloc];
179}
180
181///////////////////////////////////////////////////////////////////////////////////////////////////
182#pragma mark - Public
183
184- (void)addButtonWithTitle:(NSString *)title color:(NSString*)color block:(void (^)())block 
185{
186    [_blocks addObject:[NSArray arrayWithObjects:
187                        block ? [[block copy] autorelease] : [NSNull null],
188                        title,
189                        color,
190                        nil]];
191}
192
193- (void)addButtonWithTitle:(NSString *)title block:(void (^)())block 
194{
195    [self addButtonWithTitle:title color:@"gray" block:block];
196}
197
198- (void)setCancelButtonWithTitle:(NSString *)title block:(void (^)())block 
199{
200    [self addButtonWithTitle:title color:@"black" block:block];
201}
202
203- (void)setDestructiveButtonWithTitle:(NSString *)title block:(void (^)())block
204{
205    [self addButtonWithTitle:title color:@"red" block:block];
206}
207
208- (void)addButtonWithTitle:(NSString *)title imageIdentifier:(NSString*)identifier block:(void (^)())block {
209    [self addButtonWithTitle:title color:identifier block:block];
210}
211
212- (void)show
213{
214    _shown = YES;
215    
216    BOOL isSecondButton = NO;
217    NSUInteger index = 0;
218    for (NSUInteger i = 0; i < _blocks.count; i++)
219    {
220        NSArray *block = [_blocks objectAtIndex:i];
221        NSString *title = [block objectAtIndex:1];
222        NSString *color = [block objectAtIndex:2];
223
224        UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"alert-%@-button.png", color]];
225        image = [image stretchableImageWithLeftCapWidth:(int)(image.size.width+1)>>1 topCapHeight:0];
226        
227        UIImage *highlightedImage = [UIImage imageNamed:[NSString stringWithFormat:@"alert-%@-button-highlighted.png", color]];
228        
229        highlightedImage = [highlightedImage stretchableImageWithLeftCapWidth:(int)(highlightedImage.size.width+1)>>1 topCapHeight:0];
230        
231        CGFloat maxHalfWidth = floorf((_view.bounds.size.width-kAlertViewBorder*3)*0.5);
232        CGFloat width = _view.bounds.size.width-kAlertViewBorder*2;
233        CGFloat xOffset = kAlertViewBorder;
234        if (isSecondButton)
235        {
236            width = maxHalfWidth;
237            xOffset = width + kAlertViewBorder * 2;
238            isSecondButton = NO;
239        }
240        else if (i + 1 < _blocks.count)
241        {
242            // In this case there's another button.
243            // Let's check if they fit on the same line.
244            CGSize size = [title sizeWithFont:buttonFont 
245                                  minFontSize:10 
246                               actualFontSize:nil
247                                     forWidth:_view.bounds.size.width-kAlertViewBorder*2 
248                                lineBreakMode:NSLineBreakByClipping];
249            
250            if (size.width < maxHalfWidth - kAlertViewBorder)
251            {
252                // It might fit. Check the next Button
253                NSArray *block2 = [_blocks objectAtIndex:i+1];
254                NSString *title2 = [block2 objectAtIndex:1];
255                size = [title2 sizeWithFont:buttonFont 
256                                minFontSize:10 
257                             actualFontSize:nil
258                                   forWidth:_view.bounds.size.width-kAlertViewBorder*2 
259                              lineBreakMode:NSLineBreakByClipping];
260                
261                if (size.width < maxHalfWidth - kAlertViewBorder)
262                {
263                    // They'll fit!
264                    isSecondButton = YES;  // For the next iteration
265                    width = maxHalfWidth;
266                }
267            }
268        }
269        else if (_blocks.count  == 1)
270        {
271            // In this case this is the ony button. We'll size according to the text
272            CGSize size = [title sizeWithFont:buttonFont
273                                  minFontSize:10
274                               actualFontSize:nil
275                                     forWidth:_view.bounds.size.width-kAlertViewBorder*2
276                                lineBreakMode:NSLineBreakByClipping];
277            
278            size.width = MAX(size.width, 80);
279            if (size.width + 2 * kAlertViewBorder < width)
280            {
281                width = size.width + 2 * kAlertViewBorder;
282                xOffset = floorf((_view.bounds.size.width - width) * 0.5);
283            }
284        }
285        
286        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
287        button.frame = CGRectMake(xOffset, _height, width, kAlertButtonHeight);
288        button.titleLabel.font = buttonFont;
289        if (IOS_LESS_THAN_6) {
290#pragma clan diagnostic push
291#pragma clang diagnostic ignored "-Wdeprecated-declarations"
292            button.titleLabel.minimumFontSize = 10;
293#pragma clan diagnostic pop
294        }
295        else {
296            button.titleLabel.adjustsFontSizeToFitWidth = YES;
297            button.titleLabel.adjustsLetterSpacingToFitWidth = YES;
298            button.titleLabel.minimumScaleFactor = 0.1;
299        }
300        button.titleLabel.textAlignment = NSTextAlignmentCenter;
301        button.titleLabel.shadowOffset = kAlertViewButtonShadowOffset;
302        button.backgroundColor = [UIColor clearColor];
303        button.tag = i+1;
304        
305        [button setBackgroundImage:image forState:UIControlStateNormal];
306        if (highlightedImage)
307        {
308            [button setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];
309        }
310        [button setTitleColor:kAlertViewButtonTextColor forState:UIControlStateNormal];
311        [button setTitleShadowColor:kAlertViewButtonShadowColor forState:UIControlStateNormal];
312        [button setTitle:title forState:UIControlStateNormal];
313        button.accessibilityLabel = title;
314        
315        [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
316        
317        [_view addSubview:button];
318        
319        if (!isSecondButton)
320            _height += kAlertButtonHeight + kAlertViewBorder;
321        
322        index++;
323    }
324
325    //_height += 10;  // Margin for the shadow // not sure where this came from, but it's making things look strange (I don't see a shadow, either)
326    
327    if (_height < background.size.height)
328    {
329        CGFloat offset = background.size.height - _height;
330        _height = background.size.height;
331        CGRect frame;
332        for (NSUInteger i = 0; i < _blocks.count; i++)
333        {
334            UIButton *btn = (UIButton *)[_view viewWithTag:i+1];
335            frame = btn.frame;
336            frame.origin.y += offset;
337            btn.frame = frame;
338        }
339    }
340    
341    
342    CGRect frame = _view.frame;
343    frame.origin.y = - _height;
344    frame.size.height = _height;
345    _view.frame = frame;
346    
347    UIImageView *modalBackground = [[UIImageView alloc] initWithFrame:_view.bounds];
348    
349    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]))
350        modalBackground.image = backgroundlandscape;
351    else
352        modalBackground.image = background;
353
354    modalBackground.contentMode = UIViewContentModeScaleToFill;
355    modalBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
356    [_view insertSubview:modalBackground atIndex:0];
357    [modalBackground release];
358    
359    if (_backgroundImage)
360    {
361        [BlockBackground sharedInstance].backgroundImage = _backgroundImage;
362        [_backgroundImage release];
363        _backgroundImage = nil;
364    }
365    
366    [BlockBackground sharedInstance].vignetteBackground = _vignetteBackground;
367    [[BlockBackground sharedInstance] addToMainWindow:_view];
368
369    __block CGPoint center = _view.center;
370    center.y = floorf([BlockBackground sharedInstance].bounds.size.height * 0.5) + kAlertViewBounce;
371    
372    _cancelBounce = NO;
373    
374    [UIView animateWithDuration:0.4
375                          delay:0.0
376                        options:UIViewAnimationOptionCurveEaseOut
377                     animations:^{
378                         [BlockBackground sharedInstance].alpha = 1.0f;
379                         _view.center = center;
380                     } 
381                     completion:^(BOOL finished) {
382                         if (_cancelBounce) return;
383                         
384                         [UIView animateWithDuration:0.1
385                                               delay:0.0
386                                             options:0
387                                          animations:^{
388                                              center.y -= kAlertViewBounce;
389                                              _view.center = center;
390                                          } 
391                                          completion:^(BOOL finished) {
392                                              [[NSNotificationCenter defaultCenter] postNotificationName:@"AlertViewFinishedAnimations" object:self];
393                                          }];
394                     }];
395    
396    [self retain];
397}
398
399- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated 
400{
401    _shown = NO;
402    
403    [[NSNotificationCenter defaultCenter] removeObserver:self];
404    
405    if (buttonIndex >= 0 && buttonIndex < [_blocks count])
406    {
407        id obj = [[_blocks objectAtIndex: buttonIndex] objectAtIndex:0];
408        if (![obj isEqual:[NSNull null]])
409        {
410            ((void (^)())obj)();
411        }
412    }
413    
414    if (animated)
415    {
416        [UIView animateWithDuration:0.1
417                              delay:0.0
418                            options:0
419                         animations:^{
420                             CGPoint center = _view.center;
421                             center.y += 20;
422                             _view.center = center;
423                         } 
424                         completion:^(BOOL finished) {
425                             [UIView animateWithDuration:0.4
426                                                   delay:0.0 
427                                                 options:UIViewAnimationOptionCurveEaseIn
428                                              animations:^{
429                                                  CGRect frame = _view.frame;
430                                                  frame.origin.y = -frame.size.height;
431                                                  _view.frame = frame;
432                                                  [[BlockBackground sharedInstance] reduceAlphaIfEmpty];
433                                              } 
434                                              completion:^(BOOL finished) {
435                                                  [[BlockBackground sharedInstance] removeView:_view];
436                                                  [_view release]; _view = nil;
437                                                  [self autorelease];
438                                              }];
439                         }];
440    }
441    else
442    {
443        [[BlockBackground sharedInstance] removeView:_view];
444        [_view release]; _view = nil;
445        [self autorelease];
446    }
447}
448
449///////////////////////////////////////////////////////////////////////////////////////////////////
450#pragma mark - Action
451
452- (void)buttonClicked:(id)sender 
453{
454    /* Run the button's block */
455    int buttonIndex = [(UIButton *)sender tag] - 1;
456    [self dismissWithClickedButtonIndex:buttonIndex animated:YES];
457}
458
459@end