Apply patch

This commit is contained in:
Ali 2020-07-09 15:26:03 +04:00
parent 43c2d875a6
commit 5045b0a0ca
15 changed files with 329 additions and 88 deletions

View File

@ -224,6 +224,10 @@ public extension CALayer {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
}
func animateScaleY(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale.y", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
}
func animateRotation(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.rotation.z", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
}

View File

@ -77,7 +77,7 @@
@property (nonatomic) bool fadeDisabled;
- (void)animateIn;
- (void)animateOut;
- (void)animateOut:(BOOL)toSmallSize;
- (void)addMicLevel:(CGFloat)level;
- (void)dismiss;

View File

@ -500,7 +500,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
}
}
- (void)animateOut {
- (void)animateOut:(BOOL)toSmallSize {
_locked = false;
_animatedIn = false;
_displayLink.paused = true;
@ -511,15 +511,20 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius
_cancelTargetTranslation = 0;
_currentScale = 1.0f;
[UIView animateWithDuration:0.18 animations:^{
_innerIconWrapperView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
if (toSmallSize) {
_decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_decoration.alpha = 0.0;
_innerIconWrapperView.transform = CGAffineTransformMakeScale(0.2f, 0.2f);
_innerIconWrapperView.alpha = 0.0f;
} else {
_decoration.transform = CGAffineTransformMakeScale(0.33f, 0.33f);
_innerIconWrapperView.transform = CGAffineTransformMakeScale(0.4f, 0.4f);
}
_innerCircleView.alpha = 0.0f;
_outerCircleView.alpha = 0.0f;
_decoration.alpha = 0.0f;
self.iconView.alpha = 1.0f;
_innerIconWrapperView.alpha = 0.0f;
CGAffineTransform transform = CGAffineTransformMakeTranslation(0.0f, 100.0f);
transform = CGAffineTransformScale(transform, 0.2f, 0.2f);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,13 +4,16 @@ import Display
import AsyncDisplayKit
private final class AudioWaveformNodeParameters: NSObject {
let waveform: AudioWaveform?
let color: UIColor?
let gravity: AudioWaveformNode.Gravity?
let progress: CGFloat?
init(waveform: AudioWaveform?, color: UIColor?, progress: CGFloat?) {
init(waveform: AudioWaveform?, color: UIColor?, gravity: AudioWaveformNode.Gravity?, progress: CGFloat?) {
self.waveform = waveform
self.color = color
self.gravity = gravity
self.progress = progress
super.init()
@ -18,8 +21,16 @@ private final class AudioWaveformNodeParameters: NSObject {
}
final class AudioWaveformNode: ASDisplayNode {
enum Gravity {
case bottom
case center
}
private var waveform: AudioWaveform?
private var color: UIColor?
private var gravity: Gravity?
var progress: CGFloat? {
didSet {
@ -48,16 +59,17 @@ final class AudioWaveformNode: ASDisplayNode {
}
}
func setup(color: UIColor, waveform: AudioWaveform?) {
if self.color == nil || !self.color!.isEqual(color) || self.waveform != waveform {
func setup(color: UIColor, gravity: Gravity, waveform: AudioWaveform?) {
if self.color == nil || !self.color!.isEqual(color) || self.waveform != waveform || self.gravity != gravity {
self.color = color
self.gravity = gravity
self.waveform = waveform
self.setNeedsDisplay()
}
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return AudioWaveformNodeParameters(waveform: self.waveform, color: self.color, progress: self.progress)
return AudioWaveformNodeParameters(waveform: self.waveform, color: self.color, gravity: self.gravity, progress: self.progress)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
@ -128,12 +140,26 @@ final class AudioWaveformNode: ASDisplayNode {
diff = sampleWidth * 1.5
}
let gravityMultiplierY: CGFloat = {
switch parameters.gravity ?? .bottom {
case .bottom:
return 1
case .center:
return 0.5
}
}()
let adjustedSampleHeight = sampleHeight - diff
if adjustedSampleHeight.isLessThanOrEqualTo(sampleWidth) {
context.fillEllipse(in: CGRect(x: offset, y: size.height - sampleWidth, width: sampleWidth, height: sampleWidth))
context.fill(CGRect(x: offset, y: size.height - halfSampleWidth, width: sampleWidth, height: halfSampleWidth))
context.fillEllipse(in: CGRect(x: offset, y: (size.height - sampleWidth) * gravityMultiplierY, width: sampleWidth, height: sampleWidth))
context.fill(CGRect(x: offset, y: (size.height - halfSampleWidth) * gravityMultiplierY, width: sampleWidth, height: halfSampleWidth))
} else {
let adjustedRect = CGRect(x: offset, y: size.height - adjustedSampleHeight, width: sampleWidth, height: adjustedSampleHeight)
let adjustedRect = CGRect(
x: offset,
y: (size.height - adjustedSampleHeight) * gravityMultiplierY,
width: sampleWidth,
height: adjustedSampleHeight
)
context.fill(adjustedRect)
context.fillEllipse(in: CGRect(x: adjustedRect.minX, y: adjustedRect.minY - halfSampleWidth, width: sampleWidth, height: sampleWidth))
context.fillEllipse(in: CGRect(x: adjustedRect.minX, y: adjustedRect.maxY - halfSampleWidth, width: sampleWidth, height: sampleWidth))

View File

@ -2493,11 +2493,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedInputTextPanelState { panelState in
let isLocked = strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId
if let audioRecorder = audioRecorder {
if panelState.mediaRecordingState == nil {
return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorder, isLocked: strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId))
return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorder, isLocked: isLocked))
}
} else {
if case .waitingForPreview = panelState.mediaRecordingState {
return panelState
}
return panelState.withUpdatedMediaRecordingState(nil)
}
return panelState
@ -7316,18 +7320,30 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatDisplayNode.updateRecordedMediaDeleted(true)
break
case .preview:
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedInputTextPanelState { panelState in
return panelState.withUpdatedMediaRecordingState(.waitingForPreview)
}
})
let _ = (audioRecorderValue.takenRecordedData() |> deliverOnMainQueue).start(next: { [weak self] data in
if let strongSelf = self, let data = data {
if data.duration < 0.5 {
strongSelf.recorderFeedback?.error()
strongSelf.recorderFeedback = nil
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedInputTextPanelState { panelState in
return panelState.withUpdatedMediaRecordingState(nil)
}
})
} else if let waveform = data.waveform {
let resource = LocalFileMediaResource(fileId: arc4random64(), size: data.compressedData.count)
strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData)
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedRecordedMediaPreview(ChatRecordedMediaPreview(resource: resource, duration: Int32(data.duration), fileSize: Int32(data.compressedData.count), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5)))
$0.updatedRecordedMediaPreview(ChatRecordedMediaPreview(resource: resource, duration: Int32(data.duration), fileSize: Int32(data.compressedData.count), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5))).updatedInputTextPanelState { panelState in
return panelState.withUpdatedMediaRecordingState(nil)
}
})
strongSelf.recorderFeedback = nil
}
@ -7425,12 +7441,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
private func deleteMediaRecording() {
self.chatDisplayNode.updateRecordedMediaDeleted(true)
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
$0.updatedRecordedMediaPreview(nil)
})
}
private func sendMediaRecording() {
self.chatDisplayNode.updateRecordedMediaDeleted(false)
if let recordedMediaPreview = self.presentationInterfaceState.recordedMediaPreview {
if let _ = self.presentationInterfaceState.slowmodeState, !self.presentationInterfaceState.isScheduledMessages {
if let rect = self.chatDisplayNode.frameForInputActionButton() {

View File

@ -1045,6 +1045,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var immediatelyLayoutInputPanelAndAnimateAppearance = false
var secondaryInputPanelSize: CGSize?
var immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = false
var inputPanelNodeHandlesTransition = false
let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction)
@ -1056,11 +1057,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
}
dismissedInputPanelNode = self.inputPanelNode
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: inputPanelNode.supernode == nil ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) {
inputPanelNodeHandlesTransition = true
inputPanelNode.removeFromSupernode()
inputPanelNode.prevInputPanelNode = prevInputPanelNode
inputPanelNode.addSubnode(prevInputPanelNode)
} else {
dismissedInputPanelNode = self.inputPanelNode
}
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
self.inputPanelNode = inputPanelNode
if inputPanelNode.supernode == nil {
if inputPanelNode.supernode !== self {
immediatelyLayoutInputPanelAndAnimateAppearance = true
self.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
}
@ -1471,21 +1479,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height))
}
if let inputPanelNode = self.inputPanelNode, let apparentInputPanelFrame = apparentInputPanelFrame, !inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
if immediatelyLayoutInputPanelAndAnimateAppearance {
inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY)
inputPanelNode.alpha = 0.0
}
if !transition.isAnimated {
inputPanelNode.layer.removeAllAnimations()
if let currentDismissedInputPanelNode = self.currentDismissedInputPanelNode, inputPanelNode is ChatSearchInputPanelNode {
currentDismissedInputPanelNode.layer.removeAllAnimations()
}
}
transition.updateFrame(node: inputPanelNode, frame: apparentInputPanelFrame)
transition.updateAlpha(node: inputPanelNode, alpha: 1.0)
}
if let secondaryInputPanelNode = self.secondaryInputPanelNode, let apparentSecondaryInputPanelFrame = apparentSecondaryInputPanelFrame, !secondaryInputPanelNode.frame.equalTo(apparentSecondaryInputPanelFrame) {
if immediatelyLayoutSecondaryInputPanelAndAnimateAppearance {
secondaryInputPanelNode.frame = apparentSecondaryInputPanelFrame.offsetBy(dx: 0.0, dy: apparentSecondaryInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentSecondaryInputPanelFrame.maxY)
@ -1570,6 +1563,28 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
})
}
if let inputPanelNode = self.inputPanelNode,
let apparentInputPanelFrame = apparentInputPanelFrame,
!inputPanelNode.frame.equalTo(apparentInputPanelFrame) {
if immediatelyLayoutInputPanelAndAnimateAppearance {
inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY)
inputPanelNode.alpha = 0.0
}
if !transition.isAnimated {
inputPanelNode.layer.removeAllAnimations()
if let currentDismissedInputPanelNode = self.currentDismissedInputPanelNode, inputPanelNode is ChatSearchInputPanelNode {
currentDismissedInputPanelNode.layer.removeAllAnimations()
}
}
if inputPanelNodeHandlesTransition {
inputPanelNode.frame = apparentInputPanelFrame
inputPanelNode.alpha = 1.0
} else {
transition.updateFrame(node: inputPanelNode, frame: apparentInputPanelFrame)
transition.updateAlpha(node: inputPanelNode, alpha: 1.0)
}
}
if let dismissedInputPanelNode = dismissedInputPanelNode, dismissedInputPanelNode !== self.secondaryInputPanelNode {
var frameCompleted = false
var alphaCompleted = false

View File

@ -10,6 +10,7 @@ import AccountContext
class ChatInputPanelNode: ASDisplayNode {
var context: AccountContext?
var interfaceInteraction: ChatPanelInterfaceInteraction?
var prevInputPanelNode: ChatInputPanelNode?
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
return 0.0
@ -26,4 +27,8 @@ class ChatInputPanelNode: ASDisplayNode {
return 45.0
}
}
func canHandleTransition(from prevInputPanelNode: ChatInputPanelNode?) -> Bool {
return false
}
}

View File

@ -609,8 +609,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
} else {
waveformColor = messageTheme.mediaInactiveControlColor
}
strongSelf.waveformNode.setup(color: waveformColor, waveform: audioWaveform)
strongSelf.waveformForegroundNode.setup(color: messageTheme.mediaActiveControlColor, waveform: audioWaveform)
strongSelf.waveformNode.setup(color: waveformColor, gravity: .bottom, waveform: audioWaveform)
strongSelf.waveformForegroundNode.setup(color: messageTheme.mediaActiveControlColor, gravity: .bottom, waveform: audioWaveform)
} else if let waveformScrubbingNode = strongSelf.waveformScrubbingNode {
strongSelf.waveformScrubbingNode = nil
waveformScrubbingNode.removeFromSupernode()

View File

@ -10,6 +10,7 @@ import TelegramPresentationData
import UniversalMediaPlayer
import AppBundle
import ContextUI
import AnimationUI
private func generatePauseIcon(_ theme: PresentationTheme) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/MinimizedPause"), color: theme.chat.inputPanel.actionControlForegroundColor)
@ -24,22 +25,23 @@ extension AudioWaveformNode: CustomMediaPlayerScrubbingForegroundNode {
}
final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
private let deleteButton: HighlightableButtonNode
let deleteButton: HighlightableButtonNode
let binNode: AnimationNode
let sendButton: HighlightTrackingButtonNode
private var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
private let playButton: HighlightableButtonNode
private let pauseButton: HighlightableButtonNode
let playButton: HighlightableButtonNode
let pauseButton: HighlightableButtonNode
private let waveformButton: ASButtonNode
private let waveformBackgroundNode: ASImageNode
let waveformBackgroundNode: ASImageNode
private let waveformNode: AudioWaveformNode
private let waveformForegroundNode: AudioWaveformNode
private let waveformScubberNode: MediaPlayerScrubbingNode
let waveformScubberNode: MediaPlayerScrubbingNode
private var presentationInterfaceState: ChatPresentationInterfaceState?
private var mediaPlayer: MediaPlayer?
private let durationLabel: MediaPlayerTimeTextNode
let durationLabel: MediaPlayerTimeTextNode
private let statusDisposable = MetaDisposable()
@ -48,7 +50,19 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
init(theme: PresentationTheme) {
self.deleteButton = HighlightableButtonNode()
self.deleteButton.displaysAsynchronously = false
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: theme.chat.inputPanel.panelControlAccentColor), for: [])
self.binNode = AnimationNode(
animation: "BinBlue",
colors: [
"Cap11.Cap2.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Bin 5.Bin.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Cap12.Cap1.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Line15.Line1.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Line13.Line3.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Line14.Line2.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
"Line13.Обводка 1": theme.chat.inputPanel.panelControlAccentColor,
]
)
self.sendButton = HighlightTrackingButtonNode()
self.sendButton.displaysAsynchronously = false
@ -87,8 +101,9 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
super.init()
self.addSubnode(self.deleteButton)
self.addSubnode(self.sendButton)
self.deleteButton.addSubnode(binNode)
self.addSubnode(self.waveformBackgroundNode)
self.addSubnode(self.sendButton)
self.addSubnode(self.waveformScubberNode)
self.addSubnode(self.playButton)
self.addSubnode(self.pauseButton)
@ -144,8 +159,8 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
self.presentationInterfaceState = interfaceState
if let recordedMediaPreview = interfaceState.recordedMediaPreview, updateWaveform {
self.waveformNode.setup(color: interfaceState.theme.chat.inputPanel.actionControlForegroundColor.withAlphaComponent(0.5), waveform: recordedMediaPreview.waveform)
self.waveformForegroundNode.setup(color: interfaceState.theme.chat.inputPanel.actionControlForegroundColor, waveform: recordedMediaPreview.waveform)
self.waveformNode.setup(color: interfaceState.theme.chat.inputPanel.actionControlForegroundColor.withAlphaComponent(0.5), gravity: .center, waveform: recordedMediaPreview.waveform)
self.waveformForegroundNode.setup(color: interfaceState.theme.chat.inputPanel.actionControlForegroundColor, gravity: .center, waveform: recordedMediaPreview.waveform)
if self.mediaPlayer != nil {
self.mediaPlayer?.pause()
@ -175,8 +190,9 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
let panelHeight = defaultHeight(metrics: metrics)
transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset, y: -1.0), size: CGSize(width: 48.0, height: panelHeight)))
transition.updateFrame(node: self.deleteButton, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40)))
transition.updateFrame(node: self.sendButton, frame: CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel, y: -UIScreenPixel), size: CGSize(width: 44.0, height: panelHeight)))
self.binNode.frame = self.deleteButton.bounds
if let slowmodeState = interfaceState.slowmodeState, !interfaceState.isScheduledMessages {
let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode
@ -203,14 +219,71 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
transition.updateFrame(node: self.playButton, frame: CGRect(origin: CGPoint(x: leftInset + 52.0, y: 10.0), size: CGSize(width: 26.0, height: 26.0)))
transition.updateFrame(node: self.pauseButton, frame: CGRect(origin: CGPoint(x: leftInset + 50.0, y: 10.0), size: CGSize(width: 26.0, height: 26.0)))
transition.updateFrame(node: self.waveformBackgroundNode, frame: CGRect(origin: CGPoint(x: leftInset + 45.0, y: 7.0 - UIScreenPixel), size: CGSize(width: width - leftInset - rightInset - 90.0, height: 33.0)))
let waveformBackgroundFrame = CGRect(origin: CGPoint(x: leftInset + 45.0, y: 7.0 - UIScreenPixel), size: CGSize(width: width - leftInset - rightInset - 90.0, height: 33.0))
transition.updateFrame(node: self.waveformBackgroundNode, frame: waveformBackgroundFrame)
transition.updateFrame(node: self.waveformButton, frame: CGRect(origin: CGPoint(x: leftInset + 45.0, y: 0.0), size: CGSize(width: width - leftInset - rightInset - 90.0, height: panelHeight)))
transition.updateFrame(node: self.waveformScubberNode, frame: CGRect(origin: CGPoint(x: leftInset + 45.0 + 35.0, y: 7.0 + floor((33.0 - 13.0) / 2.0)), size: CGSize(width: width - leftInset - rightInset - 90.0 - 45.0 - 40.0, height: 13.0)))
transition.updateFrame(node: self.durationLabel, frame: CGRect(origin: CGPoint(x: width - rightInset - 90.0 - 4.0, y: 15.0), size: CGSize(width: 35.0, height: 20.0)))
prevInputPanelNode?.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight))
if let prevTextInputPanelNode = prevInputPanelNode as? ChatTextInputPanelNode {
self.prevInputPanelNode = nil
if let audioRecordingDotNode = prevTextInputPanelNode.audioRecordingDotNode {
audioRecordingDotNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
audioRecordingDotNode.layer.removeAllAnimations()
audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1.0), to: 0.0, duration: 0.15, removeOnCompletion: false)
}
if let audioRecordingTimeNode = prevTextInputPanelNode.audioRecordingTimeNode {
audioRecordingTimeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
audioRecordingTimeNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
let timePosition = audioRecordingTimeNode.position
audioRecordingTimeNode.layer.animatePosition(from: timePosition, to: CGPoint(x: timePosition.x - 20, y: timePosition.y), duration: 0.15, removeOnCompletion: false)
}
if let audioRecordingCancelIndicator = prevTextInputPanelNode.audioRecordingCancelIndicator {
audioRecordingCancelIndicator.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
prevTextInputPanelNode.actionButtons.micButton.animateOut(true)
self.deleteButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15)
self.deleteButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.playButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.5, delay: 0.15)
self.playButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15)
self.pauseButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.5, delay: 0.15)
self.pauseButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15)
self.durationLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
self.waveformScubberNode.layer.animateScaleY(from: 0.1, to: 1.0, duration: 0.5, delay: 0.15)
self.waveformScubberNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15)
self.waveformBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.waveformBackgroundNode.layer.animateFrame(
from: self.sendButton.frame.insetBy(dx: 5.5, dy: 5.5),
to: waveformBackgroundFrame,
duration: 0.3,
delay: 0.15,
timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue,
removeOnCompletion: false
) { [weak self, weak prevTextInputPanelNode] finished in
if finished, prevTextInputPanelNode?.supernode === self {
prevTextInputPanelNode?.removeFromSupernode()
}
}
}
return panelHeight
}
override func canHandleTransition(from prevInputPanelNode: ChatInputPanelNode?) -> Bool {
return prevInputPanelNode is ChatTextInputPanelNode
}
@objc func deletePressed() {
self.interfaceInteraction?.deleteRecordedMedia()
}

View File

@ -231,7 +231,7 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
if self.hasRecorder {
self.animateIn()
} else {
self.animateOut()
self.animateOut(false)
}
}
}
@ -429,11 +429,14 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
innerIconView.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
}
override func animateOut() {
super.animateOut()
innerIconView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false)
innerIconView.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, removeOnCompletion: false)
override func animateOut(_ toSmallSize: Bool) {
super.animateOut(toSmallSize)
if !toSmallSize {
micDecoration.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
innerIconView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false)
innerIconView.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, removeOnCompletion: false)
}
}
private var previousSize = CGSize()

View File

@ -906,7 +906,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
var hideMicButton = false
var audioRecordingItemsAlpha: CGFloat = 1
if let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState {
let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState
if mediaRecordingState != nil || interfaceState.recordedMediaPreview != nil {
audioRecordingItemsAlpha = 0
let audioRecordingInfoContainerNode: ASDisplayNode
@ -927,7 +928,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.audioRecordingTimeNode = audioRecordingTimeNode
audioRecordingInfoContainerNode.addSubnode(audioRecordingTimeNode)
if transition.isAnimated {
if transition.isAnimated && mediaRecordingState != nil {
animateTimeSlideIn = true
}
}
@ -938,7 +939,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
if let currentAudioRecordingCancelIndicator = self.audioRecordingCancelIndicator {
audioRecordingCancelIndicator = currentAudioRecordingCancelIndicator
} else {
animateCancelSlideIn = transition.isAnimated
animateCancelSlideIn = transition.isAnimated && mediaRecordingState != nil
audioRecordingCancelIndicator = ChatTextInputAudioRecordingCancelIndicator(theme: interfaceState.theme, strings: interfaceState.strings, cancel: { [weak self] in
self?.interfaceInteraction?.finishMediaRecording(.dismiss)
@ -947,15 +948,16 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.insertSubnode(audioRecordingCancelIndicator, at: 0)
}
let isLocked = mediaRecordingState.isLocked
let isLocked = mediaRecordingState?.isLocked ?? (interfaceState.recordedMediaPreview != nil)
var hideInfo = false
switch mediaRecordingState {
case let .audio(recorder, _):
self.actionButtons.micButton.audioRecorder = recorder
audioRecordingTimeNode.audioRecorder = recorder
case let .video(status, _):
switch status {
if let mediaRecordingState = mediaRecordingState {
switch mediaRecordingState {
case let .audio(recorder, _):
self.actionButtons.micButton.audioRecorder = recorder
audioRecordingTimeNode.audioRecorder = recorder
case let .video(status, _):
switch status {
case let .recording(recordingStatus):
audioRecordingTimeNode.videoRecordingStatus = recordingStatus
self.actionButtons.micButton.videoRecordingStatus = recordingStatus
@ -967,6 +969,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.actionButtons.micButton.videoRecordingStatus = nil
hideMicButton = true
hideInfo = true
}
case .waitingForPreview:
break
}
}
@ -1001,7 +1006,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingCancelIndicator.layer.animatePosition(from: CGPoint(x: width + audioRecordingCancelIndicator.bounds.size.width, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
}
audioRecordingCancelIndicator.updateIsDisplayingCancel(isLocked, animated: !animateCancelSlideIn)
audioRecordingCancelIndicator.updateIsDisplayingCancel(isLocked, animated: !animateCancelSlideIn && mediaRecordingState != nil)
if isLocked || self.actionButtons.micButton.cancelTranslation > cancelTransformThreshold {
var deltaOffset: CGFloat = 0.0
@ -1046,7 +1051,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingDotNode = currentAudioRecordingDotNode
} else {
self.audioRecordingDotNode?.removeFromSupernode()
audioRecordingDotNode = AnimationNode(animation: "Bin")
audioRecordingDotNode = AnimationNode(animation: "BinRed")
self.audioRecordingDotNode = audioRecordingDotNode
self.addSubnode(audioRecordingDotNode)
}
@ -1117,8 +1122,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingDotNode.layer.removeAllAnimations()
if self.isMediaDeleted {
audioRecordingDotNode.completion = dismissDotNode
audioRecordingDotNode.play()
if self.prevInputPanelNode is ChatRecordingPreviewInputPanelNode {
self.audioRecordingDotNode?.removeFromSupernode()
self.audioRecordingDotNode = nil
} else {
audioRecordingDotNode.completion = dismissDotNode
audioRecordingDotNode.play()
}
} else {
dismissDotNode()
}
@ -1333,9 +1343,80 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.updateActionButtons(hasText: hasText, hideMicButton: hideMicButton, animated: transition.isAnimated)
if let prevInputPanelNode = prevInputPanelNode {
prevInputPanelNode.frame = CGRect(origin: .zero, size: prevInputPanelNode.frame.size)
}
if let prevPreviewInputPanelNode = self.prevInputPanelNode as? ChatRecordingPreviewInputPanelNode {
self.prevInputPanelNode = nil
prevPreviewInputPanelNode.isUserInteractionEnabled = false
if self.isMediaDeleted {
func animatePosition(for previewSubnode: ASDisplayNode) {
previewSubnode.layer.animatePosition(
from: previewSubnode.position,
to: CGPoint(x: previewSubnode.position.x - 20, y: previewSubnode.position.y),
duration: 0.15
)
}
animatePosition(for: prevPreviewInputPanelNode.waveformBackgroundNode)
animatePosition(for: prevPreviewInputPanelNode.waveformScubberNode)
animatePosition(for: prevPreviewInputPanelNode.durationLabel)
animatePosition(for: prevPreviewInputPanelNode.playButton)
animatePosition(for: prevPreviewInputPanelNode.pauseButton)
}
func animateAlpha(for previewSubnode: ASDisplayNode) {
previewSubnode.layer.animateAlpha(
from: 1.0,
to: 0.0,
duration: 0.15,
removeOnCompletion: false
)
}
animateAlpha(for: prevPreviewInputPanelNode.waveformBackgroundNode)
animateAlpha(for: prevPreviewInputPanelNode.waveformScubberNode)
animateAlpha(for: prevPreviewInputPanelNode.durationLabel)
animateAlpha(for: prevPreviewInputPanelNode.playButton)
animateAlpha(for: prevPreviewInputPanelNode.pauseButton)
let dismissBin = { [weak self, weak prevPreviewInputPanelNode] in
prevPreviewInputPanelNode?.deleteButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
prevPreviewInputPanelNode?.deleteButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
self?.attachmentButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
self?.attachmentButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
if prevPreviewInputPanelNode?.supernode === self {
prevPreviewInputPanelNode?.removeFromSupernode()
}
}
if self.isMediaDeleted {
prevPreviewInputPanelNode.binNode.completion = dismissBin
prevPreviewInputPanelNode.binNode.play()
} else {
dismissBin()
}
prevPreviewInputPanelNode.sendButton.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
actionButtons.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
actionButtons.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
prevPreviewInputPanelNode.sendButton.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
return panelHeight
}
override func canHandleTransition(from prevInputPanelNode: ChatInputPanelNode?) -> Bool {
return prevInputPanelNode is ChatRecordingPreviewInputPanelNode
}
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
let baseFontSize = max(17.0, presentationInterfaceState.fontSize.baseDisplaySize)

View File

@ -63,39 +63,49 @@ enum ChatVideoRecordingStatus: Equatable {
enum ChatTextInputPanelMediaRecordingState: Equatable {
case audio(recorder: ManagedAudioRecorder, isLocked: Bool)
case video(status: ChatVideoRecordingStatus, isLocked: Bool)
case waitingForPreview
var isLocked: Bool {
switch self {
case let .audio(_, isLocked):
return isLocked
case let .video(_, isLocked):
return isLocked
case let .audio(_, isLocked):
return isLocked
case let .video(_, isLocked):
return isLocked
case .waitingForPreview:
return true
}
}
func withLocked(_ isLocked: Bool) -> ChatTextInputPanelMediaRecordingState {
switch self {
case let .audio(recorder, _):
return .audio(recorder: recorder, isLocked: isLocked)
case let .video(status, _):
return .video(status: status, isLocked: isLocked)
case let .audio(recorder, _):
return .audio(recorder: recorder, isLocked: isLocked)
case let .video(status, _):
return .video(status: status, isLocked: isLocked)
case .waitingForPreview:
return .waitingForPreview
}
}
static func ==(lhs: ChatTextInputPanelMediaRecordingState, rhs: ChatTextInputPanelMediaRecordingState) -> Bool {
switch lhs {
case let .audio(lhsRecorder, lhsIsLocked):
if case let .audio(rhsRecorder, rhsIsLocked) = rhs, lhsRecorder === rhsRecorder, lhsIsLocked == rhsIsLocked {
return true
} else {
return false
}
case let .video(status, isLocked):
if case .video(status, isLocked) = rhs {
return true
} else {
return false
}
case let .audio(lhsRecorder, lhsIsLocked):
if case let .audio(rhsRecorder, rhsIsLocked) = rhs, lhsRecorder === rhsRecorder, lhsIsLocked == rhsIsLocked {
return true
} else {
return false
}
case let .video(status, isLocked):
if case .video(status, isLocked) = rhs {
return true
} else {
return false
}
case .waitingForPreview:
if case .waitingForPreview = rhs {
return true
}
return false
}
}
}