mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
235 lines
9.1 KiB
Objective-C
235 lines
9.1 KiB
Objective-C
//
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "OWSMessagesInputToolbar.h"
|
|
#import "OWSMessagesToolbarContentView.h"
|
|
#import "UIColor+OWS.h"
|
|
#import "UIFont+OWS.h"
|
|
#import "UIView+OWS.h"
|
|
#import "ViewControllerUtils.h"
|
|
#import <SignalServiceKit/NSTimer+OWS.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@interface OWSMessagesInputToolbar () <OWSSendMessageGestureDelegate>
|
|
|
|
@property (nonatomic, nullable) UIView *voiceMemoUI;
|
|
|
|
@property (nonatomic) UIView *voiceMemoContentView;
|
|
|
|
@property (nonatomic) NSDate *voiceMemoStartTime;
|
|
|
|
@property (nonatomic, nullable) NSTimer *voiceMemoUpdateTimer;
|
|
|
|
@property (nonatomic) UILabel *recordingLabel;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation OWSMessagesInputToolbar
|
|
|
|
- (void)toggleSendButtonEnabled
|
|
{
|
|
// Do nothing; disables JSQ's control over send button enabling.
|
|
// Overrides a method in JSQMessagesInputToolbar.
|
|
}
|
|
|
|
- (JSQMessagesToolbarContentView *)loadToolbarContentView
|
|
{
|
|
NSArray *views = [[OWSMessagesToolbarContentView nib] instantiateWithOwner:nil options:nil];
|
|
OWSAssert(views.count == 1);
|
|
OWSMessagesToolbarContentView *view = views[0];
|
|
OWSAssert([view isKindOfClass:[OWSMessagesToolbarContentView class]]);
|
|
view.sendMessageGestureDelegate = self;
|
|
return view;
|
|
}
|
|
|
|
- (void)showVoiceMemoUI
|
|
{
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
self.voiceMemoStartTime = [NSDate date];
|
|
|
|
[self.voiceMemoUI removeFromSuperview];
|
|
|
|
self.voiceMemoUI = [UIView new];
|
|
self.voiceMemoUI.userInteractionEnabled = NO;
|
|
self.voiceMemoUI.backgroundColor = [UIColor whiteColor];
|
|
[self addSubview:self.voiceMemoUI];
|
|
self.voiceMemoUI.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
|
|
|
|
self.voiceMemoContentView = [UIView new];
|
|
[self.voiceMemoUI addSubview:self.voiceMemoContentView];
|
|
[self.voiceMemoContentView autoPinWidthToSuperview];
|
|
[self.voiceMemoContentView autoPinHeightToSuperview];
|
|
|
|
self.recordingLabel = [UILabel new];
|
|
self.recordingLabel.textColor = [UIColor ows_destructiveRedColor];
|
|
self.recordingLabel.font = [UIFont ows_mediumFontWithSize:14.f];
|
|
[self.voiceMemoContentView addSubview:self.recordingLabel];
|
|
[self updateVoiceMemo];
|
|
|
|
UIImage *icon = [UIImage imageNamed:@"voice-memo-button"];
|
|
OWSAssert(icon);
|
|
UIImageView *imageView =
|
|
[[UIImageView alloc] initWithImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
|
|
imageView.tintColor = [UIColor ows_destructiveRedColor];
|
|
[self.voiceMemoContentView addSubview:imageView];
|
|
|
|
NSMutableAttributedString *cancelString = [NSMutableAttributedString new];
|
|
const CGFloat cancelArrowFontSize = ScaleFromIPhone5To7Plus(18.4, 20.f);
|
|
const CGFloat cancelFontSize = ScaleFromIPhone5To7Plus(14.f, 16.f);
|
|
[cancelString
|
|
appendAttributedString:[[NSAttributedString alloc]
|
|
initWithString:@"\uf104 "
|
|
attributes:@{
|
|
NSFontAttributeName : [UIFont ows_fontAwesomeFont:cancelArrowFontSize],
|
|
NSForegroundColorAttributeName : [UIColor ows_destructiveRedColor],
|
|
NSBaselineOffsetAttributeName : @(-1.f),
|
|
}]];
|
|
[cancelString
|
|
appendAttributedString:[[NSAttributedString alloc]
|
|
initWithString:NSLocalizedString(@"VOICE_MESSAGE_CANCEL_INSTRUCTIONS",
|
|
@"Indicates how to cancel a voice message.")
|
|
attributes:@{
|
|
NSFontAttributeName : [UIFont ows_mediumFontWithSize:cancelFontSize],
|
|
NSForegroundColorAttributeName : [UIColor ows_destructiveRedColor],
|
|
}]];
|
|
[cancelString
|
|
appendAttributedString:[[NSAttributedString alloc]
|
|
initWithString:@" \uf104"
|
|
attributes:@{
|
|
NSFontAttributeName : [UIFont ows_fontAwesomeFont:cancelArrowFontSize],
|
|
NSForegroundColorAttributeName : [UIColor ows_destructiveRedColor],
|
|
NSBaselineOffsetAttributeName : @(-1.f),
|
|
}]];
|
|
UILabel *cancelLabel = [UILabel new];
|
|
cancelLabel.attributedText = cancelString;
|
|
[self.voiceMemoContentView addSubview:cancelLabel];
|
|
|
|
const CGFloat kRedCircleSize = 100.f;
|
|
UIView *redCircleView = [UIView new];
|
|
redCircleView.backgroundColor = [UIColor ows_destructiveRedColor];
|
|
redCircleView.layer.cornerRadius = kRedCircleSize * 0.5f;
|
|
[redCircleView autoSetDimension:ALDimensionWidth toSize:kRedCircleSize];
|
|
[redCircleView autoSetDimension:ALDimensionHeight toSize:kRedCircleSize];
|
|
[self.voiceMemoContentView addSubview:redCircleView];
|
|
[redCircleView autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.contentView.rightBarButtonItem];
|
|
[redCircleView autoAlignAxis:ALAxisVertical toSameAxisOfView:self.contentView.rightBarButtonItem];
|
|
|
|
UIImage *whiteIcon = [UIImage imageNamed:@"voice-message-large-white"];
|
|
OWSAssert(whiteIcon);
|
|
UIImageView *whiteIconView = [[UIImageView alloc] initWithImage:whiteIcon];
|
|
[redCircleView addSubview:whiteIconView];
|
|
[whiteIconView autoCenterInSuperview];
|
|
|
|
[imageView autoVCenterInSuperview];
|
|
[imageView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:10];
|
|
[self.recordingLabel autoVCenterInSuperview];
|
|
[self.recordingLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:imageView withOffset:5.f];
|
|
[cancelLabel autoVCenterInSuperview];
|
|
[cancelLabel autoHCenterInSuperview];
|
|
[self.voiceMemoUI setNeedsLayout];
|
|
[self.voiceMemoUI layoutSubviews];
|
|
|
|
// Slide in the "slide to cancel" label.
|
|
CGRect cancelLabelStartFrame = cancelLabel.frame;
|
|
CGRect cancelLabelEndFrame = cancelLabel.frame;
|
|
cancelLabelStartFrame.origin.x = self.voiceMemoUI.bounds.size.width;
|
|
cancelLabel.frame = cancelLabelStartFrame;
|
|
[UIView animateWithDuration:0.35f
|
|
delay:0.f
|
|
options:UIViewAnimationOptionCurveEaseOut
|
|
animations:^{
|
|
cancelLabel.frame = cancelLabelEndFrame;
|
|
}
|
|
completion:nil];
|
|
|
|
// Pulse the icon.
|
|
imageView.layer.opacity = 1.f;
|
|
[UIView animateWithDuration:0.5f
|
|
delay:0.2f
|
|
options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
|
|
| UIViewAnimationOptionCurveEaseIn
|
|
animations:^{
|
|
imageView.layer.opacity = 0.f;
|
|
}
|
|
completion:nil];
|
|
|
|
// Fade in the view.
|
|
self.voiceMemoUI.layer.opacity = 0.f;
|
|
[UIView animateWithDuration:0.2f
|
|
animations:^{
|
|
self.voiceMemoUI.layer.opacity = 1.f;
|
|
}
|
|
completion:^(BOOL finished) {
|
|
if (finished) {
|
|
self.voiceMemoUI.layer.opacity = 1.f;
|
|
}
|
|
}];
|
|
|
|
[self.voiceMemoUpdateTimer invalidate];
|
|
self.voiceMemoUpdateTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.1f
|
|
target:self
|
|
selector:@selector(updateVoiceMemo)
|
|
userInfo:nil
|
|
repeats:YES];
|
|
}
|
|
|
|
- (void)hideVoiceMemoUI:(BOOL)animated
|
|
{
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
UIView *oldVoiceMemoUI = self.voiceMemoUI;
|
|
self.voiceMemoUI = nil;
|
|
NSTimer *voiceMemoUpdateTimer = self.voiceMemoUpdateTimer;
|
|
self.voiceMemoUpdateTimer = nil;
|
|
|
|
[oldVoiceMemoUI.layer removeAllAnimations];
|
|
|
|
if (animated) {
|
|
[UIView animateWithDuration:0.35f
|
|
animations:^{
|
|
oldVoiceMemoUI.layer.opacity = 0.f;
|
|
}
|
|
completion:^(BOOL finished) {
|
|
[oldVoiceMemoUI removeFromSuperview];
|
|
[voiceMemoUpdateTimer invalidate];
|
|
}];
|
|
} else {
|
|
[oldVoiceMemoUI removeFromSuperview];
|
|
[voiceMemoUpdateTimer invalidate];
|
|
}
|
|
}
|
|
|
|
- (void)setVoiceMemoUICancelAlpha:(CGFloat)cancelAlpha
|
|
{
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
// Fade out the voice message views as the cancel gesture
|
|
// proceeds as feedback.
|
|
self.voiceMemoContentView.layer.opacity = MAX(0.f, MIN(1.f, 1.f - (float)cancelAlpha));
|
|
}
|
|
|
|
- (void)updateVoiceMemo
|
|
{
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
NSTimeInterval durationSeconds = fabs([self.voiceMemoStartTime timeIntervalSinceNow]);
|
|
self.recordingLabel.text = [ViewControllerUtils formatDurationSeconds:(long)round(durationSeconds)];
|
|
[self.recordingLabel sizeToFit];
|
|
}
|
|
|
|
#pragma mark - OWSSendMessageGestureDelegate
|
|
|
|
- (void)sendMessageGestureRecognized
|
|
{
|
|
OWSAssert(self.sendButtonOnRight);
|
|
[self.delegate messagesInputToolbar:self didPressRightBarButton:self.contentView.rightBarButtonItem];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|