WKNumberPadViewController.mm   [plain text]


/*
 * Copyright (C) 2018 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "WKNumberPadViewController.h"

#if PLATFORM(WATCHOS)

#import "UIKitSPI.h"
#import "WKNumberPadView.h"
#import <WebCore/LocalizedStrings.h>
#import <wtf/RetainPtr.h>
#import <wtf/WeakObjCPtr.h>
#import <wtf/text/WTFString.h>

static const CGFloat numberPadViewTopMargin = 30;
static const CGFloat headerButtonWidth = 24;
static const CGFloat inputLabelMinimumScale = 0.7;
static const CGFloat numberPadViewDismissAnimationDuration = 0.3;
static const NSTimeInterval numberPadDeleteKeyRepeatDelay = 0.35;
static const NSTimeInterval numberPadDeleteKeyRepeatInterval = 0.1;
static CGFloat inputLabelFontSize()
{
    if ([[UIDevice currentDevice] puic_deviceVariant] == PUICDeviceVariantCompact)
        return 16;
    return 18;
}

@implementation WKNumberPadViewController {
    RetainPtr<NSMutableString> _inputText;
    RetainPtr<WKNumberPadView> _numberPadView;
    WKNumberPadInputMode _inputMode;
    RetainPtr<UILabel> _inputLabel;
    RetainPtr<UIButton> _deleteButton;
    RetainPtr<UIButton> _backChevronButton;
    BOOL _shouldDismissWithFadeAnimation;
}

- (instancetype)initWithDelegate:(id <PUICQuickboardViewControllerDelegate>)delegate initialText:(NSString *)initialText inputMode:(WKNumberPadInputMode)inputMode
{
    if (!(self = [super initWithDelegate:delegate]))
        return nil;

    _inputText = adoptNS(initialText.mutableCopy);
    _inputMode = inputMode;
    _shouldDismissWithFadeAnimation = NO;
    return self;
}

- (void)dealloc
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [super dealloc];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    _numberPadView = adoptNS([[WKNumberPadView alloc] initWithFrame:UIRectInset(self.contentView.bounds, numberPadViewTopMargin, 0, 0, 0) controller:self]);
    [self.contentView addSubview:_numberPadView.get()];

    _inputLabel = adoptNS([[UILabel alloc] init]);
    [_inputLabel setFont:[UIFont systemFontOfSize:inputLabelFontSize() weight:UIFontWeightSemibold]];
    [_inputLabel setTextColor:[UIColor whiteColor]];
    [_inputLabel setLineBreakMode:NSLineBreakByTruncatingHead];
    [_inputLabel setTextAlignment:NSTextAlignmentCenter];
    [_inputLabel setMinimumScaleFactor:inputLabelMinimumScale];
    [_inputLabel setAdjustsFontSizeToFitWidth:YES];
    [self.headerView addSubview:_inputLabel.get()];

    _deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
    UIImage *deleteButtonIcon = [[PUICResources imageNamed:@"keypad-delete-glyph" inBundle:[NSBundle bundleWithIdentifier:@"com.apple.PepperUICore"] shouldCache:YES] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    [_deleteButton setImage:deleteButtonIcon forState:UIControlStateNormal];
    [_deleteButton setTintColor:[UIColor systemRedColor]];
    [_deleteButton addTarget:self action:@selector(_startDeletionTimer) forControlEvents:UIControlEventTouchDown];
    [_deleteButton addTarget:self action:@selector(_deleteButtonPressed) forControlEvents:UIControlEventTouchUpInside];
    [_deleteButton addTarget:self action:@selector(_cancelDeletionTimers) forControlEvents:UIControlEventTouchUpOutside];
    [self.headerView addSubview:_deleteButton.get()];

    _backChevronButton = [UIButton buttonWithType:UIButtonTypeCustom];
    UIImage *backChevronButtonIcon = [[PUICResources imageNamed:@"status-bar-chevron" inBundle:[NSBundle bundleWithIdentifier:@"com.apple.PepperUICore"] shouldCache:YES] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    [_backChevronButton setImage:backChevronButtonIcon forState:UIControlStateNormal];
    [_backChevronButton setTintColor:[UIColor systemGrayColor]];
    [_backChevronButton addTarget:self action:@selector(_cancelInput) forControlEvents:UIControlEventTouchUpInside];
    [self.headerView addSubview:_backChevronButton.get()];

    [self _reloadHeaderViewFromInputText];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self _cancelDeletionTimers];
}

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];

    [_inputLabel setFrame:UIRectInsetEdges(self.headerView.bounds, UIRectEdgeLeft | UIRectEdgeRight, headerButtonWidth)];
    [_deleteButton setFrame:CGRectMake(CGRectGetWidth(self.headerView.bounds) - headerButtonWidth, 0, headerButtonWidth, CGRectGetHeight(self.headerView.bounds))];
    [_backChevronButton setFrame:CGRectMake(0, 0, headerButtonWidth, CGRectGetHeight(self.headerView.bounds))];
}

- (void)_reloadHeaderViewFromInputText
{
    BOOL hasInputText = [_inputText length];
    self.cancelButton.hidden = hasInputText;
    [_deleteButton setHidden:!hasInputText];
    [_backChevronButton setHidden:!hasInputText];
    [_inputLabel setText:_inputText.get()];
}

- (void)didSelectKey:(WKNumberPadKey)key
{
    [self _handleKeyPress:key];
}

- (void)_handleKeyPress:(WKNumberPadKey)key
{
    switch (key) {
    case WKNumberPadKeyDash:
        [_inputText appendString:@"-"];
        break;
    case WKNumberPadKeyAsterisk:
        [_inputText appendString:@"*"];
        break;
    case WKNumberPadKeyOctothorpe:
        [_inputText appendString:@"#"];
        break;
    case WKNumberPadKeyClosingParenthesis:
        [_inputText appendString:@")"];
        break;
    case WKNumberPadKeyOpeningParenthesis:
        [_inputText appendString:@"("];
        break;
    case WKNumberPadKeyPlus:
        [_inputText appendString:@"+"];
        break;
    case WKNumberPadKeyAccept:
        [self.delegate quickboard:self textEntered:[[[NSAttributedString alloc] initWithString:_inputText.get()] autorelease]];
        return;
    case WKNumberPadKey0:
        [_inputText appendString:@"0"];
        break;
    case WKNumberPadKey1:
        [_inputText appendString:@"1"];
        break;
    case WKNumberPadKey2:
        [_inputText appendString:@"2"];
        break;
    case WKNumberPadKey3:
        [_inputText appendString:@"3"];
        break;
    case WKNumberPadKey4:
        [_inputText appendString:@"4"];
        break;
    case WKNumberPadKey5:
        [_inputText appendString:@"5"];
        break;
    case WKNumberPadKey6:
        [_inputText appendString:@"6"];
        break;
    case WKNumberPadKey7:
        [_inputText appendString:@"7"];
        break;
    case WKNumberPadKey8:
        [_inputText appendString:@"8"];
        break;
    case WKNumberPadKey9:
        [_inputText appendString:@"9"];
        break;
    default:
        break;
    }

    [self _cancelDeletionTimers];
    [self _reloadHeaderViewFromInputText];
}

- (void)_cancelInput
{
    _shouldDismissWithFadeAnimation = YES;
    [self.delegate quickboardInputCancelled:self];
}

- (void)_deleteLastInputCharacter
{
    if (![_inputText length])
        return;

    [_inputText deleteCharactersInRange:NSMakeRange([_inputText length] - 1, 1)];
    [self _reloadHeaderViewFromInputText];
}

- (void)_deleteButtonPressed
{
    [self _deleteLastInputCharacter];
    [self _cancelDeletionTimers];
}

- (void)_cancelDeletionTimers
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_startDeletionTimer) object:nil];
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_deletionTimerFired) object:nil];
}

- (void)_startDeletionTimer
{
    [self _cancelDeletionTimers];
    [self performSelector:@selector(_deletionTimerFired) withObject:nil afterDelay:numberPadDeleteKeyRepeatDelay];
}

- (void)_deletionTimerFired
{
    [self _cancelDeletionTimers];
    [self _deleteLastInputCharacter];
    if ([_inputText length])
        [self performSelector:@selector(_deletionTimerFired) withObject:nil afterDelay:numberPadDeleteKeyRepeatInterval];
}

#pragma mark - PUICQuickboardViewController overrides

- (void)addContentViewAnimations:(BOOL)isPresenting
{
    if (!_shouldDismissWithFadeAnimation) {
        [super addContentViewAnimations:isPresenting];
        return;
    }

    CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeOutAnimation.fromValue = @1;
    fadeOutAnimation.toValue = @0;
    fadeOutAnimation.duration = numberPadViewDismissAnimationDuration;
    fadeOutAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
#if USE(APPLE_INTERNAL_SDK)
    [self.contentView addAnimation:fadeOutAnimation forKey:@"WebKitNumberPadFadeOutAnimationKey"];
#endif
    self.contentView.alpha = 0;
}

@end

#endif // PLATFORM(WATCHOS)