Files
Pariece McKinney b14e5cfcb0 Public Release 3.0
2024-03-28 19:39:04 -04:00

337 lines
12 KiB
Objective-C

/*
Copyright (c) 2015, 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.
3. Neither the name of the copyright holder(s) nor the names of any contributors
may be used to endorse or promote products derived from this software without
specific prior written permission. No license is granted to the trademarks of
the copyright holders even if such marks are included in this software.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT OWNER OR 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 "ORKAudioContentView.h"
#import "ORKAudioMeteringView.h"
#import "ORKHeadlineLabel.h"
#import "ORKLabel.h"
#import "ORKAccessibility.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
#import "ORKRecordButton.h"
// The central blue region.
static const CGFloat GraphViewBlueZoneHeight = 170;
// The two bands at top and bottom which are "loud" each have this height.
static const CGFloat GraphViewRedZoneHeight = 25;
static const CGFloat ORKAudioStepContentRecordButtonVerticalSpacing = 20.0;
@interface ORKAudioTimerLabel : ORKLabel
@end
@implementation ORKAudioTimerLabel
+ (UIFont *)defaultFont {
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
UIFontDescriptor *alternativeDescriptor = ORKFontDescriptorForLightStylisticAlternative(descriptor);
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize] + 4];
}
@end
@interface ORKAudioContentView () <ORKRecordButtonDelegate>
@property (nonatomic, strong) ORKHeadlineLabel *alertLabel;
@property (nonatomic, strong) UILabel *timerLabel;
@property (nonatomic, strong) ORKAudioMeteringView *graphView;
@property (nonatomic, copy, nullable) ORKAudioStepContentViewEventHandler viewEventhandler;
@end
@implementation ORKAudioContentView {
NSMutableArray *_samples;
UIColor *_keyColor;
ORKRecordButton *_recordButton;
BOOL _checkAudioLevel;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.layoutMargins = ORKStandardFullScreenLayoutMarginsForView(self);
_checkAudioLevel = YES;
_useRecordButton = NO;
self.alertLabel = [ORKHeadlineLabel new];
_alertLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.timerLabel = [ORKAudioTimerLabel new];
_timerLabel.translatesAutoresizingMaskIntoConstraints = NO;
_timerLabel.textAlignment = NSTextAlignmentRight;
self.graphView = [[ORKAudioMeteringView alloc] init];
_graphView.translatesAutoresizingMaskIntoConstraints = NO;
self.translatesAutoresizingMaskIntoConstraints = NO;
self.alertColor = [UIColor ork_redColor];
[self addSubview:_alertLabel];
[self addSubview:_timerLabel];
[self addSubview:_graphView];
_alertLabel.text = ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
// _timerLabel.text set in -updateTimerLabel:
self.alertThreshold = GraphViewBlueZoneHeight / ((GraphViewRedZoneHeight * 2) + GraphViewBlueZoneHeight);
[self updateGraphSamples];
[self applyKeyColor];
[self setUpConstraints];
}
return self;
}
- (void)tintColorDidChange {
[self applyKeyColor];
}
- (void)setFailed:(BOOL)failed {
_failed = failed;
_alertLabel.text = failed ? ORKLocalizedString(@"AUDIO_GENERIC_ERROR_LABEL", nil) : ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
[self updateAlertLabelHidden];
}
- (void)setFinished:(BOOL)finished {
_finished = finished;
[self updateAlertLabelHidden];
}
- (void)setUseRecordButton:(BOOL)useRecordButton {
_useRecordButton = useRecordButton;
if (_useRecordButton) {
_checkAudioLevel = NO;
[_timerLabel setHidden: YES];
[self setupRecordButton];
[self setUpConstraints];
}
}
- (void)applyKeyColor {
UIColor *keyColor = [self keyColor];
_timerLabel.textColor = keyColor;
_graphView.meterColor = keyColor;
}
- (UIColor *)keyColor {
return _keyColor ? : [self tintColor];
}
- (void)setKeyColor:(UIColor *)keyColor {
_keyColor = keyColor;
[self applyKeyColor];
}
- (void)setAlertColor:(UIColor *)alertColor {
_alertColor = alertColor;
_alertLabel.textColor = alertColor;
_graphView.alertColor = alertColor;
}
- (void)setViewEventHandler:(ORKAudioStepContentViewEventHandler)handler {
self.viewEventhandler = [handler copy];
}
- (void)invokeViewEventHandlerWithEvent:(ORKAudioContentViewEvent)event {
if (self.viewEventhandler) {
dispatch_async(dispatch_get_main_queue(), ^{
self.viewEventhandler(event);
});
}
}
- (void)buttonPressed:(ORKRecordButton *)recordButton {
switch (recordButton.buttonType) {
case ORKRecordButtonTypeRecord:
[self invokeViewEventHandlerWithEvent:ORKAudioContentViewEventStartRecording];
[_recordButton setButtonType:ORKRecordButtonTypeStop];
break;
default:
[self invokeViewEventHandlerWithEvent:ORKAudioContentViewEventStopRecording];
[_recordButton setButtonState:ORKRecordButtonStateDisabled];
break;
}
}
- (void)setupRecordButton {
if (!_recordButton) {
_recordButton = [[ORKRecordButton alloc] init];
_recordButton.delegate = self;
_recordButton.translatesAutoresizingMaskIntoConstraints = NO;
[_recordButton setButtonType:ORKRecordButtonTypeRecord];
[self addSubview:_recordButton];
}
}
- (void)setUpConstraints {
NSMutableArray *constraints = [NSMutableArray array];
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _alertLabel, _graphView);
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_graphView]-[_alertLabel]|"
options:(NSLayoutFormatOptions)0
metrics:nil
views:views]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_alertLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
const CGFloat sideMargin = self.layoutMargins.left + (2 * ORKStandardLeftMarginForTableViewCell(self));
const CGFloat innerMargin = 2;
if (_useRecordButton) {
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-sideMargin-[_graphView]-sideMargin-|"
options:NSLayoutFormatAlignAllCenterY
metrics:@{@"sideMargin": @(sideMargin)}
views:views]];
[constraints addObject:[_recordButton.topAnchor constraintEqualToAnchor:_graphView.bottomAnchor constant:ORKAudioStepContentRecordButtonVerticalSpacing]];
[constraints addObject:[_recordButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor]];
} else {
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-sideMargin-[_graphView]-innerMargin-[_timerLabel]-sideMargin-|"
options:NSLayoutFormatAlignAllCenterY
metrics:@{@"sideMargin": @(sideMargin), @"innerMargin": @(innerMargin)}
views:views]];
}
[constraints addObject:[NSLayoutConstraint constraintWithItem:_graphView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:(GraphViewBlueZoneHeight + GraphViewRedZoneHeight * 2)]];
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)setAlertThreshold:(CGFloat)alertThreshold {
_alertThreshold = alertThreshold;
_graphView.alertThreshold = alertThreshold;
[self updateGraphSamples];
}
- (void)setTimeLeft:(NSTimeInterval)timeLeft {
_timeLeft = timeLeft;
[self updateTimerLabel];
}
- (void)updateTimerLabel {
static NSDateComponentsFormatter *formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateComponentsFormatter new];
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
});
NSString *string = [formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
_timerLabel.text = string;
_timerLabel.hidden = (string == nil);
}
- (void)updateGraphSamples {
_graphView.samples = _samples;
[self updateAlertLabelHidden];
}
- (void)updateAlertLabelHidden {
NSNumber *sample = _samples.lastObject;
if (_checkAudioLevel) {
BOOL show = (!_finished && (sample.doubleValue > _alertThreshold)) || _failed;
if (_alertLabel.hidden && show) {
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, _alertLabel.text);
}
_alertLabel.hidden = !show;
}
}
- (void)setSamples:(NSArray *)samples {
_samples = [samples mutableCopy];
[self updateGraphSamples];
}
- (void)addSample:(NSNumber *)sample {
NSAssert(sample != nil, @"Sample should be non-nil");
if (!_samples) {
_samples = [NSMutableArray array];
}
[_samples addObject:sample];
// Try to keep around 250 samples
if (_samples.count > 500) {
_samples = [[_samples subarrayWithRange:(NSRange){250, _samples.count - 250}] mutableCopy];
}
[self updateGraphSamples];
}
- (void)removeAllSamples {
_samples = nil;
[self updateGraphSamples];
}
#pragma mark Accessibility
- (BOOL)isAccessibilityElement {
return YES;
}
- (NSString *)accessibilityLabel {
NSString *timerAxString = _timerLabel.isHidden ? nil : _timerLabel.accessibilityLabel;
NSString *alertAxString = _alertLabel.isHidden ? nil : _alertLabel.accessibilityLabel;
return ORKAccessibilityStringForVariables(ORKLocalizedString(@"AX_AUDIO_BAR_GRAPH", nil), timerAxString, alertAxString);
}
- (UIAccessibilityTraits)accessibilityTraits {
return [super accessibilityTraits] | UIAccessibilityTraitUpdatesFrequently;
}
@end