From 59050c71a4d23630041a2316cdce24bb132ff9d4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 16 Jul 2020 12:51:47 +0400 Subject: [PATCH] Apply patch --- .../TGModernConversationInputMicButton.h | 1 - .../TGModernConversationInputMicButton.m | 1 - submodules/TelegramUI/Sources/BlobView.swift | 193 ++++++++++-------- .../ChatMessageInteractiveFileNode.swift | 92 ++++++--- .../ChatTextInputMediaRecordingButton.swift | 8 +- 5 files changed, 174 insertions(+), 121 deletions(-) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h index 0e2b1fd7dc..271e7c16b7 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h @@ -11,7 +11,6 @@ @protocol TGModernConversationInputMicButtonDecoration - (void)updateLevel:(CGFloat)level; -- (void)tick:(CGFloat)level; - (void)setColor:(UIColor *)color; - (void)stopAnimating; - (void)startAnimating; diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index b500bfe64d..ceb5f73284 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -846,7 +846,6 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius NSTimeInterval t = CACurrentMediaTime(); _currentLevel = _currentLevel * 0.9f + _inputLevel * 0.1f; - [_decoration tick:_currentLevel]; _currentTranslation = MIN(0.0, _currentTranslation * 0.7f + _targetTranslation * 0.3f); _cancelTranslation = MIN(0.0, _cancelTranslation * 0.7f + _cancelTargetTranslation * 0.3f); diff --git a/submodules/TelegramUI/Sources/BlobView.swift b/submodules/TelegramUI/Sources/BlobView.swift index 95d67f987e..4c62a3741a 100644 --- a/submodules/TelegramUI/Sources/BlobView.swift +++ b/submodules/TelegramUI/Sources/BlobView.swift @@ -3,53 +3,81 @@ import UIKit import Display import LegacyComponents -private enum Constants { - - static let maxLevel: CGFloat = 4 -} - final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration { - private let smallBlob = BlobView( - pointsCount: 8, - minRandomness: 0.1, - maxRandomness: 0.5, - minSpeed: 0.2, - maxSpeed: 0.6, - minScale: 0.45, - maxScale: 0.55, - scaleSpeed: 0.2, - isCircle: true - ) - private let mediumBlob = BlobView( - pointsCount: 8, - minRandomness: 1, - maxRandomness: 1, - minSpeed: 1.5, - maxSpeed: 7, - minScale: 0.52, - maxScale: 0.87, - scaleSpeed: 0.2, - isCircle: false - ) - private let bigBlob = BlobView( - pointsCount: 8, - minRandomness: 1, - maxRandomness: 1, - minSpeed: 1.5, - maxSpeed: 7, - minScale: 0.57, - maxScale: 1, - scaleSpeed: 0.2, - isCircle: false - ) + private let smallBlob: BlobView + private let mediumBlob: BlobView + private let bigBlob: BlobView - override init(frame: CGRect) { + private let maxLevel: CGFloat + + private var displayLinkAnimator: ConstantDisplayLinkAnimator? + + private var audioLevel: CGFloat = 0 + private var presentationAudioLevel: CGFloat = 0 + + private(set) var isAnimating = false + + typealias BlobRange = (min: CGFloat, max: CGFloat) + + init( + frame: CGRect, + maxLevel: CGFloat, + smallBlobRange: BlobRange, + mediumBlobRange: BlobRange, + bigBlobRange: BlobRange + ) { + self.maxLevel = maxLevel + + self.smallBlob = BlobView( + pointsCount: 8, + minRandomness: 0.1, + maxRandomness: 0.5, + minSpeed: 0.2, + maxSpeed: 0.6, + minScale: smallBlobRange.min, + maxScale: smallBlobRange.max, + scaleSpeed: 0.2, + isCircle: true + ) + self.mediumBlob = BlobView( + pointsCount: 8, + minRandomness: 1, + maxRandomness: 1, + minSpeed: 1.5, + maxSpeed: 7, + minScale: mediumBlobRange.min, + maxScale: mediumBlobRange.max, + scaleSpeed: 0.2, + isCircle: false + ) + self.bigBlob = BlobView( + pointsCount: 8, + minRandomness: 1, + maxRandomness: 1, + minSpeed: 1.5, + maxSpeed: 7, + minScale: bigBlobRange.min, + maxScale: bigBlobRange.max, + scaleSpeed: 0.2, + isCircle: false + ) + super.init(frame: frame) addSubview(bigBlob) addSubview(mediumBlob) addSubview(smallBlob) + + displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in + guard let strongSelf = self else { return } + + strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1 + + strongSelf.smallBlob.level = strongSelf.presentationAudioLevel + strongSelf.mediumBlob.level = strongSelf.presentationAudioLevel + strongSelf.bigBlob.level = strongSelf.presentationAudioLevel + } } required init?(coder: NSCoder) { @@ -63,45 +91,61 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration } func updateLevel(_ level: CGFloat) { - let normalizedLevel = min(1, max(level / Constants.maxLevel, 0)) + let normalizedLevel = min(1, max(level / maxLevel, 0)) smallBlob.updateSpeedLevel(to: normalizedLevel) mediumBlob.updateSpeedLevel(to: normalizedLevel) bigBlob.updateSpeedLevel(to: normalizedLevel) - } - - func tick(_ level: CGFloat) { - let normalizedLevel = min(1, max(level / Constants.maxLevel, 0)) - smallBlob.level = normalizedLevel - mediumBlob.level = normalizedLevel - bigBlob.level = normalizedLevel + audioLevel = normalizedLevel } func startAnimating() { - mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.1, removeOnCompletion: false) - bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.1, removeOnCompletion: false) + guard !isAnimating else { return } + isAnimating = true + + mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false) + bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false) + + updateBlobsState() + + displayLinkAnimator?.isPaused = false } func stopAnimating() { - mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.1, removeOnCompletion: false) - bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.1, removeOnCompletion: false) + guard isAnimating else { return } + isAnimating = false + + mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, removeOnCompletion: false) + bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, removeOnCompletion: false) + + updateBlobsState() + + displayLinkAnimator?.isPaused = true + } + + private func updateBlobsState() { + if isAnimating { + if smallBlob.frame.size != .zero { + smallBlob.startAnimating() + mediumBlob.startAnimating() + bigBlob.startAnimating() + } + } else { + smallBlob.stopAnimating() + mediumBlob.stopAnimating() + bigBlob.stopAnimating() + } } override func layoutSubviews() { super.layoutSubviews() - let isInitial = smallBlob.frame == .zero - smallBlob.frame = bounds mediumBlob.frame = bounds bigBlob.frame = bounds - if isInitial { - smallBlob.startAnimating() - mediumBlob.startAnimating() - bigBlob.startAnimating() - } + updateBlobsState() } } @@ -221,40 +265,19 @@ final class BlobView: UIView { animateToNewShape() } - func animateToNewScale() { - let scaleLevelForAnimation: CGFloat = { - if scaleLevelsToBalance.isEmpty { - return 0 - } - return scaleLevelsToBalance.reduce(0, +) / CGFloat(scaleLevelsToBalance.count) - }() - let isDownscale = lastScaleLevel > scaleLevelForAnimation - lastScaleLevel = scaleLevelForAnimation - - shapeLayer.pop_removeAnimation(forKey: "scale") - - let currentScale = minScale + (maxScale - minScale) * scaleLevelForAnimation - let scaleAnimation = POPBasicAnimation(propertyNamed: kPOPLayerScaleXY)! - scaleAnimation.toValue = CGPoint(x: currentScale, y: currentScale) - scaleAnimation.duration = isDownscale ? 0.45 : CFTimeInterval(scaleSpeed) - scaleAnimation.completionBlock = { [weak self] animation, finished in - if finished { - self?.animateToNewScale() - } - } - shapeLayer.pop_add(scaleAnimation, forKey: "scale") - - scaleLevel = 0 - scaleLevelsToBalance.removeAll() + func stopAnimating() { + fromPoints = currentPoints + toPoints = nil + pop_removeAnimation(forKey: "blob") } - func animateToNewShape() { + private func animateToNewShape() { guard !isCircle else { return } if pop_animation(forKey: "blob") != nil { fromPoints = currentPoints toPoints = nil - shapeLayer.pop_removeAnimation(forKey: "blob") + pop_removeAnimation(forKey: "blob") } if fromPoints == nil { diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 781cc7ef99..bcb1b5ae6c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -34,7 +34,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var iconNode: TransformImageNode? private var statusNode: SemanticStatusNode? private var playbackAudioLevelView: VoiceBlobView? - private var displayLinkAnimator: ConstantDisplayLinkAnimator? private var streamingStatusNode: RadialStatusNode? private var tapRecognizer: UITapGestureRecognizer? @@ -63,21 +62,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { var visibility: Bool = false { didSet { - if self.visibility != oldValue { - if self.visibility { - if self.displayLinkAnimator == nil { - self.displayLinkAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.currentAudioLevel = strongSelf.currentAudioLevel * 0.9 + strongSelf.inputAudioLevel * 0.1 - strongSelf.playbackAudioLevelView?.tick(strongSelf.currentAudioLevel) - }) - } - self.displayLinkAnimator?.isPaused = false - } else { - self.displayLinkAnimator?.isPaused = true - } + guard self.visibility != oldValue else { return } + + if !self.visibility { + self.playbackAudioLevelView?.stopAnimating() } } } @@ -449,8 +437,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { if hasThumbnail { minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 86.0 } else if isVoice { + var descriptionAndStatusWidth = descriptionLayout.size.width + if let statusSize = statusSize { + descriptionAndStatusWidth += 6 + statusSize.width + } let calcDuration = max(minVoiceLength, min(maxVoiceLength, CGFloat(audioDuration))) minLayoutWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength) + minLayoutWidth = max(descriptionAndStatusWidth + 56, minLayoutWidth) } else { minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 44.0 + 8.0 } @@ -477,7 +470,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } return (minLayoutWidth, { boundingWidth in - let progressDiameter: CGFloat = (isVoice && !hasThumbnail) ? 37.0 : 44.0 + let progressDiameter: CGFloat = 44.0 var iconFrame: CGRect? let progressFrame: CGRect @@ -487,10 +480,19 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { if hasThumbnail { let currentIconFrame = CGRect(origin: CGPoint(x: -1.0, y: -7.0), size: CGSize(width: 74.0, height: 74.0)) iconFrame = currentIconFrame - progressFrame = CGRect(origin: CGPoint(x: currentIconFrame.minX + floor((currentIconFrame.size.width - progressDiameter) / 2.0), y: currentIconFrame.minY + floor((currentIconFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) + progressFrame = CGRect( + origin: CGPoint( + x: currentIconFrame.minX + floor((currentIconFrame.size.width - progressDiameter) / 2.0), + y: currentIconFrame.minY + floor((currentIconFrame.size.height - progressDiameter) / 2.0) + ), + size: CGSize(width: progressDiameter, height: progressDiameter) + ) controlAreaWidth = 86.0 } else { - progressFrame = CGRect(origin: CGPoint(x: 0.0, y: isVoice ? -5.0 : 0.0), size: CGSize(width: progressDiameter, height: progressDiameter)) + progressFrame = CGRect( + origin: CGPoint(x: 3.0, y: -3.0), + size: CGSize(width: progressDiameter, height: progressDiameter) + ) controlAreaWidth = progressFrame.maxX + 8.0 } @@ -506,7 +508,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let descriptionFrame: CGRect if isVoice { - descriptionFrame = CGRect(origin: CGPoint(x: 43.0, y: 19.0), size: descriptionLayout.size) + descriptionFrame = CGRect(origin: CGPoint(x: 56.0, y: 22.0), size: descriptionLayout.size) } else { descriptionFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY - 1.0), size: descriptionLayout.size) } @@ -516,7 +518,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let textSizes = titleFrame.union(descriptionFrame).size fittedLayoutSize = CGSize(width: textSizes.width + controlAreaWidth, height: 59.0) } else if isVoice { - fittedLayoutSize = CGSize(width: minLayoutWidth, height: 27.0) + fittedLayoutSize = CGSize(width: minLayoutWidth, height: 38.0) } else { let unionSize = titleFrame.union(descriptionFrame).union(progressFrame).size fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height + 6.0) @@ -529,8 +531,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) { - fittedLayoutSize.height += statusFrameValue.height - statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: statusFrameValue.height) + let intersection = descriptionFrame.intersection(statusFrameValue) + let addedWidth = intersection.width + 20 + fittedLayoutSize.width += addedWidth } if let statusFrameValue = statusFrame, let iconFrame = iconFrame, iconFrame.intersects(statusFrameValue) { fittedLayoutSize.height += 15.0 @@ -598,7 +601,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.waveformScrubbingNode = waveformScrubbingNode strongSelf.addSubnode(waveformScrubbingNode) } - strongSelf.waveformScrubbingNode?.frame = CGRect(origin: CGPoint(x: 43.0, y: -1.0), size: CGSize(width: boundingWidth - 41.0, height: 12.0)) + strongSelf.waveformScrubbingNode?.frame = CGRect(origin: CGPoint(x: 57.0, y: 1.0), size: CGSize(width: boundingWidth - 60.0, height: 15.0)) let waveformColor: UIColor if incoming { if consumableContentIcon != nil { @@ -679,7 +682,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.waveformNode.displaysAsynchronously = !presentationData.isPreview strongSelf.statusNode?.displaysAsynchronously = !presentationData.isPreview strongSelf.statusNode?.frame = progressFrame - strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -20.0, dy: -20.0) + strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0) strongSelf.progressFrame = progressFrame strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame strongSelf.fileIconImage = fileIconImage @@ -860,19 +863,35 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor) self.statusNode = statusNode statusNode.frame = progressFrame - - if self.playbackAudioLevelView == nil, false { - let playbackAudioLevelView = VoiceBlobView(frame: progressFrame.insetBy(dx: -20.0, dy: -20.0)) - playbackAudioLevelView.setColor(presentationData.theme.theme.chat.inputPanel.actionControlFillColor) - self.playbackAudioLevelView = playbackAudioLevelView - self.view.addSubview(playbackAudioLevelView) - } - self.addSubnode(statusNode) } else if let statusNode = self.statusNode { statusNode.backgroundNodeColor = backgroundNodeColor } + if state != .none && isVoice && self.playbackAudioLevelView == nil { + let blobFrame = progressFrame.insetBy(dx: -12.0, dy: -12.0) + let playbackAudioLevelView = VoiceBlobView( + frame: blobFrame, + maxLevel: 0.3, + smallBlobRange: (0, 0), + mediumBlobRange: (0.7, 0.8), + bigBlobRange: (0.8, 0.9) + ) + self.playbackAudioLevelView = playbackAudioLevelView + self.view.addSubview(playbackAudioLevelView) + + let maskRect = CGRect(origin: .zero, size: blobFrame.size) + let playbackMaskLayer = CAShapeLayer() + playbackMaskLayer.frame = maskRect + playbackMaskLayer.fillRule = .evenOdd + let maskPath = UIBezierPath() + maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22)) + maskPath.append(UIBezierPath(rect: maskRect)) + playbackMaskLayer.path = maskPath.cgPath + playbackAudioLevelView.layer.mask = playbackMaskLayer + } + self.playbackAudioLevelView?.setColor(presentationData.theme.theme.chat.inputPanel.actionControlFillColor) + if streamingState != .none && self.streamingStatusNode == nil { let streamingStatusNode = RadialStatusNode(backgroundNodeColor: .clear) self.streamingStatusNode = streamingStatusNode @@ -893,6 +912,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { statusNode?.removeFromSupernode() } }) + + switch state { + case .pause: + self.playbackAudioLevelView?.startAnimating() + default: + self.playbackAudioLevelView?.stopAnimating() + } } if let streamingStatusNode = self.streamingStatusNode { diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index 4d3e8e61a8..fa8a5881a7 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -238,7 +238,13 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto } private lazy var micDecoration: (UIView & TGModernConversationInputMicButtonDecoration) = { - let blobView = VoiceBlobView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 220.0, height: 220.0))) + let blobView = VoiceBlobView( + frame: CGRect(origin: CGPoint(), size: CGSize(width: 220.0, height: 220.0)), + maxLevel: 4, + smallBlobRange: (0.45, 0.55), + mediumBlobRange: (0.52, 0.87), + bigBlobRange: (0.57, 1.00) + ) blobView.setColor(self.theme.chat.inputPanel.actionControlFillColor) return blobView }()