mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Voice Note Lock
This commit is contained in:
parent
a566145d5b
commit
d29ce740cb
|
@ -437,6 +437,7 @@
|
|||
45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */; };
|
||||
45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; };
|
||||
4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */; };
|
||||
4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */; };
|
||||
4C04F58421C860C50090D0BB /* MantlePerfTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C04F58321C860C50090D0BB /* MantlePerfTest.swift */; };
|
||||
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; };
|
||||
4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */; };
|
||||
|
@ -1149,6 +1150,7 @@
|
|||
45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonCallKitCallUIAdaptee.swift; sourceTree = "<group>"; };
|
||||
45FBC59A1DF8575700E9B410 /* CallKitCallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallKitCallManager.swift; sourceTree = "<group>"; };
|
||||
45FBC5D01DF8592E00E9B410 /* SignalCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalCall.swift; sourceTree = "<group>"; };
|
||||
4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceNoteLock.swift; sourceTree = "<group>"; };
|
||||
4C04F58321C860C50090D0BB /* MantlePerfTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MantlePerfTest.swift; path = Models/MantlePerfTest.swift; sourceTree = "<group>"; };
|
||||
4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = "<group>"; };
|
||||
4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = "<group>"; };
|
||||
|
@ -2330,6 +2332,7 @@
|
|||
450D19111F85236600970622 /* RemoteVideoView.h */,
|
||||
450D19121F85236600970622 /* RemoteVideoView.m */,
|
||||
34B6A902218B3F62007C4606 /* TypingIndicatorView.swift */,
|
||||
4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */,
|
||||
);
|
||||
name = Views;
|
||||
path = views;
|
||||
|
@ -3394,6 +3397,7 @@
|
|||
34386A52207D0C01009F5D9C /* HomeViewCell.m in Sources */,
|
||||
34DC9BD921543E0C00FDDCEC /* DebugContactsUtils.m in Sources */,
|
||||
34DBF007206C3CB200025978 /* OWSBubbleShapeView.m in Sources */,
|
||||
4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */,
|
||||
34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */,
|
||||
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */,
|
||||
3496956E21A301A100DCFE74 /* OWSBackupExportJob.m in Sources */,
|
||||
|
|
|
@ -19,11 +19,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)voiceMemoGestureDidStart;
|
||||
|
||||
- (void)voiceMemoGestureDidEnd;
|
||||
- (void)voiceMemoGestureDidLock;
|
||||
|
||||
- (void)voiceMemoGestureDidComplete;
|
||||
|
||||
- (void)voiceMemoGestureDidCancel;
|
||||
|
||||
- (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha;
|
||||
- (void)voiceMemoGestureDidUpdateCancelWithRatioComplete:(CGFloat)cancelAlpha;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -55,10 +57,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)updateFontSizes;
|
||||
|
||||
- (void)updateLayoutWithSafeAreaInsets:(UIEdgeInsets)safeAreaInsets;
|
||||
- (void)ensureTextViewHeight;
|
||||
|
||||
#pragma mark - Voice Memo
|
||||
|
||||
- (void)ensureTextViewHeight;
|
||||
- (void)lockVoiceMemoUI;
|
||||
|
||||
- (void)showVoiceMemoUI;
|
||||
|
||||
- (void)hideVoiceMemoUI:(BOOL)animated;
|
||||
|
@ -67,6 +71,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)cancelVoiceMemoIfNecessary;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@property (nonatomic, nullable) OWSQuotedReplyModel *quotedReply;
|
||||
|
||||
@property (nonatomic, nullable, readonly) OWSLinkPreviewDraft *linkPreviewDraft;
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_CLOSED_ENUM(NSUInteger, VoiceMemoRecordingState){
|
||||
VoiceMemoRecordingState_Idle,
|
||||
VoiceMemoRecordingState_RecordingHeld,
|
||||
VoiceMemoRecordingState_RecordingLocked
|
||||
};
|
||||
|
||||
static void *kConversationInputTextViewObservingContext = &kConversationInputTextViewObservingContext;
|
||||
|
||||
const CGFloat kMinTextViewHeight = 36;
|
||||
|
@ -62,11 +68,16 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
#pragma mark - Voice Memo Recording UI
|
||||
|
||||
@property (nonatomic, nullable) UIView *voiceMemoUI;
|
||||
@property (nonatomic, nullable) VoiceMemoLockView *voiceMemoLockView;
|
||||
@property (nonatomic, nullable) UIView *voiceMemoContentView;
|
||||
@property (nonatomic) NSDate *voiceMemoStartTime;
|
||||
@property (nonatomic, nullable) NSTimer *voiceMemoUpdateTimer;
|
||||
@property (nonatomic) UIGestureRecognizer *voiceMemoGestureRecognizer;
|
||||
@property (nonatomic, nullable) UILabel *voiceMemoCancelLabel;
|
||||
@property (nonatomic, nullable) UIView *voiceMemoRedRecordingCircle;
|
||||
@property (nonatomic, nullable) UILabel *recordingLabel;
|
||||
@property (nonatomic) BOOL isRecordingVoiceMemo;
|
||||
@property (nonatomic, readonly) BOOL isRecordingVoiceMemo;
|
||||
@property (nonatomic) VoiceMemoRecordingState voiceMemoRecordingState;
|
||||
@property (nonatomic) CGPoint voiceMemoGestureStartLocation;
|
||||
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *layoutContraints;
|
||||
@property (nonatomic) UIEdgeInsets receivedSafeAreaInsets;
|
||||
|
@ -164,6 +175,7 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
UILongPressGestureRecognizer *longPressGestureRecognizer =
|
||||
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
|
||||
longPressGestureRecognizer.minimumPressDuration = 0;
|
||||
self.voiceMemoGestureRecognizer = longPressGestureRecognizer;
|
||||
[self.voiceMemoButton addGestureRecognizer:longPressGestureRecognizer];
|
||||
|
||||
self.userInteractionEnabled = YES;
|
||||
|
@ -435,18 +447,25 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
case UIGestureRecognizerStateFailed:
|
||||
if (self.isRecordingVoiceMemo) {
|
||||
// Cancel voice message if necessary.
|
||||
self.isRecordingVoiceMemo = NO;
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_Idle;
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateBegan:
|
||||
if (self.isRecordingVoiceMemo) {
|
||||
// Cancel voice message if necessary.
|
||||
self.isRecordingVoiceMemo = NO;
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
switch (self.voiceMemoRecordingState) {
|
||||
case VoiceMemoRecordingState_Idle:
|
||||
break;
|
||||
case VoiceMemoRecordingState_RecordingHeld:
|
||||
OWSFailDebug(@"while recording held, shouldn't be possible to restart gesture.");
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
break;
|
||||
case VoiceMemoRecordingState_RecordingLocked:
|
||||
OWSFailDebug(@"once locked, shouldn't be possible to interact with gesture.");
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
break;
|
||||
}
|
||||
// Start voice message.
|
||||
self.isRecordingVoiceMemo = YES;
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_RecordingHeld;
|
||||
self.voiceMemoGestureStartLocation = [sender locationInView:self];
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidStart];
|
||||
break;
|
||||
|
@ -457,25 +476,62 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
// For LTR/RTL, swiping in either direction will cancel.
|
||||
// This is okay because there's only space on screen to perform the
|
||||
// gesture in one direction.
|
||||
CGFloat offset = fabs(self.voiceMemoGestureStartLocation.x - location.x);
|
||||
CGFloat xOffset = fabs(self.voiceMemoGestureStartLocation.x - location.x);
|
||||
// The lower this value, the easier it is to cancel by accident.
|
||||
// The higher this value, the harder it is to cancel.
|
||||
const CGFloat kCancelOffsetPoints = 100.f;
|
||||
CGFloat cancelAlpha = offset / kCancelOffsetPoints;
|
||||
CGFloat cancelAlpha = xOffset / kCancelOffsetPoints;
|
||||
BOOL isCancelled = cancelAlpha >= 1.f;
|
||||
if (isCancelled) {
|
||||
self.isRecordingVoiceMemo = NO;
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_Idle;
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
break;
|
||||
} else {
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidChange:cancelAlpha];
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidUpdateCancelWithRatioComplete:cancelAlpha];
|
||||
}
|
||||
|
||||
CGFloat yOffset = fabs(self.voiceMemoGestureStartLocation.y - location.y);
|
||||
|
||||
// require a certain threshold before we consider the user to be
|
||||
// interacting with the lock ui, otherwise there's perceptible wobble
|
||||
// of the lock slider even when the user isn't intended to interact with it.
|
||||
const CGFloat kLockThresholdPoints = 20.f;
|
||||
const CGFloat kLockOffsetPoints = 80.f;
|
||||
CGFloat yOffsetBeyondThreshold = MAX(yOffset - kLockThresholdPoints, 0);
|
||||
CGFloat lockAlpha = yOffsetBeyondThreshold / kLockOffsetPoints;
|
||||
BOOL isLocked = lockAlpha >= 1.f;
|
||||
if (isLocked) {
|
||||
switch (self.voiceMemoRecordingState) {
|
||||
case VoiceMemoRecordingState_RecordingHeld:
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_RecordingLocked;
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidLock];
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidUpdateCancelWithRatioComplete:0];
|
||||
break;
|
||||
case VoiceMemoRecordingState_RecordingLocked:
|
||||
// already locked
|
||||
break;
|
||||
case VoiceMemoRecordingState_Idle:
|
||||
OWSFailDebug(@"failure: unexpeceted idle state");
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
[self.voiceMemoLockView updateWithRatioComplete:lockAlpha];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateEnded:
|
||||
if (self.isRecordingVoiceMemo) {
|
||||
// End voice message.
|
||||
self.isRecordingVoiceMemo = NO;
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidEnd];
|
||||
switch (self.voiceMemoRecordingState) {
|
||||
case VoiceMemoRecordingState_Idle:
|
||||
break;
|
||||
case VoiceMemoRecordingState_RecordingHeld:
|
||||
// End voice message.
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_Idle;
|
||||
[self.inputToolbarDelegate voiceMemoGestureDidComplete];
|
||||
break;
|
||||
case VoiceMemoRecordingState_RecordingLocked:
|
||||
// Continue recording.
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -483,6 +539,17 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
|
||||
#pragma mark - Voice Memo
|
||||
|
||||
- (BOOL)isRecordingVoiceMemo
|
||||
{
|
||||
switch (self.voiceMemoRecordingState) {
|
||||
case VoiceMemoRecordingState_Idle:
|
||||
return NO;
|
||||
case VoiceMemoRecordingState_RecordingHeld:
|
||||
case VoiceMemoRecordingState_RecordingLocked:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showVoiceMemoUI
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
@ -490,9 +557,9 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
self.voiceMemoStartTime = [NSDate date];
|
||||
|
||||
[self.voiceMemoUI removeFromSuperview];
|
||||
[self.voiceMemoLockView removeFromSuperview];
|
||||
|
||||
self.voiceMemoUI = [UIView new];
|
||||
self.voiceMemoUI.userInteractionEnabled = NO;
|
||||
self.voiceMemoUI.backgroundColor = Theme.toolbarBackgroundColor;
|
||||
[self addSubview:self.voiceMemoUI];
|
||||
self.voiceMemoUI.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
|
||||
|
@ -505,6 +572,14 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
self.recordingLabel.textColor = [UIColor ows_destructiveRedColor];
|
||||
self.recordingLabel.font = [UIFont ows_mediumFontWithSize:14.f];
|
||||
[self.voiceMemoContentView addSubview:self.recordingLabel];
|
||||
|
||||
VoiceMemoLockView *voiceMemoLockView = [VoiceMemoLockView new];
|
||||
self.voiceMemoLockView = voiceMemoLockView;
|
||||
[self addSubview:voiceMemoLockView];
|
||||
[voiceMemoLockView autoPinTrailingToSuperviewMargin];
|
||||
[voiceMemoLockView autoPinEdge:ALEdgeBottom toEdge:ALEdgeTop ofView:self.voiceMemoContentView];
|
||||
[voiceMemoLockView setCompressionResistanceHigh];
|
||||
|
||||
[self updateVoiceMemo];
|
||||
|
||||
UIImage *icon = [UIImage imageNamed:@"voice-memo-button"];
|
||||
|
@ -512,6 +587,7 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
UIImageView *imageView =
|
||||
[[UIImageView alloc] initWithImage:[icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
|
||||
imageView.tintColor = [UIColor ows_destructiveRedColor];
|
||||
[imageView setContentHuggingHigh];
|
||||
[self.voiceMemoContentView addSubview:imageView];
|
||||
|
||||
NSMutableAttributedString *cancelString = [NSMutableAttributedString new];
|
||||
|
@ -559,11 +635,13 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
NSBaselineOffsetAttributeName : @(-1.f),
|
||||
}]];
|
||||
UILabel *cancelLabel = [UILabel new];
|
||||
self.voiceMemoCancelLabel = cancelLabel;
|
||||
cancelLabel.attributedText = cancelString;
|
||||
[self.voiceMemoContentView addSubview:cancelLabel];
|
||||
|
||||
const CGFloat kRedCircleSize = 100.f;
|
||||
UIView *redCircleView = [UIView new];
|
||||
self.voiceMemoRedRecordingCircle = redCircleView;
|
||||
redCircleView.backgroundColor = [UIColor ows_destructiveRedColor];
|
||||
redCircleView.layer.cornerRadius = kRedCircleSize * 0.5f;
|
||||
[redCircleView autoSetDimension:ALDimensionWidth toSize:kRedCircleSize];
|
||||
|
@ -593,6 +671,17 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
cancelLabelStartFrame.origin.x
|
||||
= (CurrentAppContext().isRTL ? -self.voiceMemoUI.bounds.size.width : self.voiceMemoUI.bounds.size.width);
|
||||
cancelLabel.frame = cancelLabelStartFrame;
|
||||
|
||||
voiceMemoLockView.transform = CGAffineTransformMakeScale(0.0, 0.0);
|
||||
[voiceMemoLockView layoutIfNeeded];
|
||||
[UIView animateWithDuration:0.2f
|
||||
delay:1.f
|
||||
options:0
|
||||
animations:^{
|
||||
voiceMemoLockView.transform = CGAffineTransformIdentity;
|
||||
}
|
||||
completion:nil];
|
||||
|
||||
[UIView animateWithDuration:0.35f
|
||||
delay:0.f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
|
@ -636,11 +725,19 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_Idle;
|
||||
|
||||
UIView *oldVoiceMemoUI = self.voiceMemoUI;
|
||||
UIView *oldVoiceMemoLockView = self.voiceMemoLockView;
|
||||
|
||||
self.voiceMemoUI = nil;
|
||||
self.voiceMemoCancelLabel = nil;
|
||||
self.voiceMemoRedRecordingCircle = nil;
|
||||
self.voiceMemoContentView = nil;
|
||||
self.voiceMemoLockView = nil;
|
||||
self.recordingLabel = nil;
|
||||
NSTimer *voiceMemoUpdateTimer = self.voiceMemoUpdateTimer;
|
||||
|
||||
[self.voiceMemoUpdateTimer invalidate];
|
||||
self.voiceMemoUpdateTimer = nil;
|
||||
|
||||
[oldVoiceMemoUI.layer removeAllAnimations];
|
||||
|
@ -649,17 +746,77 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
[UIView animateWithDuration:0.35f
|
||||
animations:^{
|
||||
oldVoiceMemoUI.layer.opacity = 0.f;
|
||||
oldVoiceMemoLockView.layer.opacity = 0.f;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[oldVoiceMemoUI removeFromSuperview];
|
||||
[voiceMemoUpdateTimer invalidate];
|
||||
[oldVoiceMemoLockView removeFromSuperview];
|
||||
}];
|
||||
} else {
|
||||
[oldVoiceMemoUI removeFromSuperview];
|
||||
[voiceMemoUpdateTimer invalidate];
|
||||
[oldVoiceMemoLockView removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)lockVoiceMemoUI
|
||||
{
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
UIButton *sendVoiceMemoButton = [[OWSButton alloc] initWithBlock:^{
|
||||
[weakSelf.inputToolbarDelegate voiceMemoGestureDidComplete];
|
||||
}];
|
||||
[sendVoiceMemoButton setTitle:MessageStrings.sendButton forState:UIControlStateNormal];
|
||||
[sendVoiceMemoButton setTitleColor:UIColor.ows_signalBlueColor forState:UIControlStateNormal];
|
||||
sendVoiceMemoButton.alpha = 0;
|
||||
[self.voiceMemoContentView addSubview:sendVoiceMemoButton];
|
||||
[sendVoiceMemoButton autoPinEdgesToSuperviewMarginsExcludingEdge:ALEdgeLeading];
|
||||
[sendVoiceMemoButton setCompressionResistanceHigh];
|
||||
[sendVoiceMemoButton setContentHuggingHigh];
|
||||
|
||||
UIButton *cancelButton = [[OWSButton alloc] initWithBlock:^{
|
||||
[weakSelf.inputToolbarDelegate voiceMemoGestureDidCancel];
|
||||
}];
|
||||
[cancelButton setTitle:CommonStrings.cancelButton forState:UIControlStateNormal];
|
||||
[cancelButton setTitleColor:UIColor.ows_destructiveRedColor forState:UIControlStateNormal];
|
||||
cancelButton.alpha = 0;
|
||||
cancelButton.titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
[self.voiceMemoContentView addSubview:cancelButton];
|
||||
OWSAssert(self.recordingLabel != nil);
|
||||
[self.recordingLabel setContentHuggingHigh];
|
||||
|
||||
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow
|
||||
forConstraints:^{
|
||||
[cancelButton autoHCenterInSuperview];
|
||||
}];
|
||||
[cancelButton autoPinEdge:ALEdgeLeading
|
||||
toEdge:ALEdgeTrailing
|
||||
ofView:self.recordingLabel
|
||||
withOffset:4
|
||||
relation:NSLayoutRelationGreaterThanOrEqual];
|
||||
[cancelButton autoPinEdge:ALEdgeTrailing
|
||||
toEdge:ALEdgeLeading
|
||||
ofView:sendVoiceMemoButton
|
||||
withOffset:-4
|
||||
relation:NSLayoutRelationLessThanOrEqual];
|
||||
[cancelButton autoVCenterInSuperview];
|
||||
|
||||
[self.voiceMemoContentView layoutIfNeeded];
|
||||
[UIView animateWithDuration:0.35
|
||||
animations:^{
|
||||
self.voiceMemoCancelLabel.alpha = 0;
|
||||
self.voiceMemoRedRecordingCircle.alpha = 0;
|
||||
self.voiceMemoLockView.transform = CGAffineTransformMakeScale(0, 0);
|
||||
cancelButton.alpha = 1.0;
|
||||
sendVoiceMemoButton.alpha = 1.0;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[self.voiceMemoCancelLabel removeFromSuperview];
|
||||
[self.voiceMemoRedRecordingCircle removeFromSuperview];
|
||||
[self.voiceMemoLockView removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setVoiceMemoUICancelAlpha:(CGFloat)cancelAlpha
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
@ -681,7 +838,7 @@ const CGFloat kMaxTextViewHeight = 98;
|
|||
- (void)cancelVoiceMemoIfNecessary
|
||||
{
|
||||
if (self.isRecordingVoiceMemo) {
|
||||
self.isRecordingVoiceMemo = NO;
|
||||
self.voiceMemoRecordingState = VoiceMemoRecordingState_Idle;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConversationScrollButton.h"
|
||||
|
@ -86,12 +86,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
if (self.hasUnreadMessages) {
|
||||
foregroundColor = UIColor.whiteColor;
|
||||
backgroundColor = UIColor.ows_materialBlueColor;
|
||||
} else if (Theme.isDarkThemeEnabled) {
|
||||
foregroundColor = UIColor.ows_materialBlueColor;
|
||||
backgroundColor = [UIColor colorWithWhite:0.25f alpha:1.f];
|
||||
} else {
|
||||
foregroundColor = UIColor.ows_materialBlueColor;
|
||||
backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.f];
|
||||
backgroundColor = Theme.scrollButtonBackgroundColor;
|
||||
}
|
||||
|
||||
const CGFloat circleSize = self.class.circleSize;
|
||||
|
|
|
@ -4033,17 +4033,25 @@ typedef enum : NSUInteger {
|
|||
[self requestRecordingVoiceMemo];
|
||||
}
|
||||
|
||||
- (void)voiceMemoGestureDidEnd
|
||||
- (void)voiceMemoGestureDidComplete
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
OWSLogInfo(@"voiceMemoGestureDidEnd");
|
||||
OWSLogInfo(@"");
|
||||
|
||||
[self.inputToolbar hideVoiceMemoUI:YES];
|
||||
[self endRecordingVoiceMemo];
|
||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
|
||||
- (void)voiceMemoGestureDidLock
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSLogInfo(@"");
|
||||
|
||||
[self.inputToolbar lockVoiceMemoUI];
|
||||
}
|
||||
|
||||
- (void)voiceMemoGestureDidCancel
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
@ -4055,7 +4063,7 @@ typedef enum : NSUInteger {
|
|||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
|
||||
- (void)voiceMemoGestureDidChange:(CGFloat)cancelAlpha
|
||||
- (void)voiceMemoGestureDidUpdateCancelWithRatioComplete:(CGFloat)cancelAlpha
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
|
|
80
Signal/src/views/VoiceNoteLock.swift
Normal file
80
Signal/src/views/VoiceNoteLock.swift
Normal file
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc
|
||||
public class VoiceMemoLockView: UIView {
|
||||
|
||||
private var offsetConstraint: NSLayoutConstraint!
|
||||
|
||||
private let offsetFromToolbar: CGFloat = 40
|
||||
private let backgroundViewInitialHeight: CGFloat = 80
|
||||
private var chevronTravel: CGFloat {
|
||||
return -1 * (backgroundViewInitialHeight - 50)
|
||||
}
|
||||
|
||||
@objc
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
addSubview(backgroundView)
|
||||
backgroundView.addSubview(lockIconView)
|
||||
backgroundView.addSubview(chevronView)
|
||||
|
||||
layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: offsetFromToolbar, trailing: 0)
|
||||
|
||||
backgroundView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
|
||||
self.offsetConstraint = backgroundView.autoPinEdge(toSuperviewMargin: .bottom)
|
||||
// we anchor the top so that the bottom "slides up" to meet it as the user slides the lock
|
||||
backgroundView.autoPinEdge(.top, to: .bottom, of: self, withOffset: -offsetFromToolbar - backgroundViewInitialHeight)
|
||||
|
||||
backgroundView.layoutMargins = UIEdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 6)
|
||||
|
||||
lockIconView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
|
||||
chevronView.autoPinEdges(toSuperviewMarginsExcludingEdge: .top)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public func update(ratioComplete: CGFloat) {
|
||||
offsetConstraint.constant = CGFloatLerp(0, chevronTravel, ratioComplete)
|
||||
}
|
||||
|
||||
// MARK: - Subviews
|
||||
|
||||
private lazy var lockIconView: UIImageView = {
|
||||
let imageTemplate = #imageLiteral(resourceName: "ic_lock_outline").withRenderingMode(.alwaysTemplate)
|
||||
let imageView = UIImageView(image: imageTemplate)
|
||||
imageView.tintColor = .ows_destructiveRed
|
||||
imageView.autoSetDimensions(to: CGSize(width: 24, height: 24))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var chevronView: UIView = {
|
||||
let label = UILabel()
|
||||
label.text = "\u{2303}"
|
||||
label.textColor = .ows_destructiveRed
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var backgroundView: UIView = {
|
||||
let view = UIView()
|
||||
|
||||
let width: CGFloat = 36
|
||||
view.autoSetDimension(.width, toSize: width)
|
||||
view.backgroundColor = Theme.scrollButtonBackgroundColor
|
||||
view.layer.cornerRadius = width / 2
|
||||
view.layer.borderColor = Theme.offBackgroundColor.cgColor
|
||||
view.layer.borderWidth = CGHairlineWidth()
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIColor+OWS.h"
|
||||
|
@ -61,6 +61,8 @@ extern NSString *const ThemeDidChangeNotification;
|
|||
@property (class, readonly, nonatomic) UIColor *toastForegroundColor;
|
||||
@property (class, readonly, nonatomic) UIColor *toastBackgroundColor;
|
||||
|
||||
@property (class, readonly, nonatomic) UIColor *scrollButtonBackgroundColor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Theme.h"
|
||||
|
@ -194,6 +194,12 @@ NSString *const ThemeKeyThemeEnabled = @"ThemeKeyThemeEnabled";
|
|||
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray75Color : UIColor.ows_gray60Color);
|
||||
}
|
||||
|
||||
+ (UIColor *)scrollButtonBackgroundColor
|
||||
{
|
||||
return Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:0.25f alpha:1.f]
|
||||
: [UIColor colorWithWhite:0.95f alpha:1.f];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
Loading…
Reference in a new issue