diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h index 973373fec2..6b83406e04 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h @@ -2,6 +2,13 @@ @class TGModernConversationInputMicButton; +@protocol TGModernConversationInputMicButtonDecoration + +- (void)updateLevel:(CGFloat)level; +- (void)tick:(CGFloat)level; + +@end + @protocol TGModernConversationInputMicButtonPresentation - (UIView *)view; @@ -26,6 +33,7 @@ - (bool)micButtonShouldLock; - (id)micButtonPresenter; +- (UIView *)micButtonDecoration; @end diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index 10ca479f92..79c693110b 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -135,6 +135,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius UIImage *_icon; id _presentation; + UIView *_decoration; } @end @@ -240,6 +241,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius centerPoint.y += _centerOffset.y; _innerCircleView.center = centerPoint; _outerCircleView.center = centerPoint; + _decoration.center = centerPoint; _innerIconWrapperView.center = centerPoint; _lockPanelWrapperView.frame = CGRectMake(floor(centerPoint.x - _lockPanelWrapperView.frame.size.width / 2.0f), floor(centerPoint.y - 122.0f - _lockPanelWrapperView.frame.size.height / 2.0f), _lockPanelWrapperView.frame.size.width, _lockPanelWrapperView.frame.size.height); @@ -355,7 +357,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius [delegate micButtonInteractionRequestedLockedAction]; }; } - + _lockPanelWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 38.0f, 77.0f)]; _lockPanelWrapperView.userInteractionEnabled = false; [[_presentation view] addSubview:_lockPanelWrapperView]; @@ -377,6 +379,12 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerCircleView.alpha = 0.0f; [[_presentation view] addSubview:_innerCircleView]; + if ([_delegate respondsToSelector:@selector(micButtonDecoration)]) { + UIView *decoration = [_delegate micButtonDecoration]; + _decoration = decoration; + [[_presentation view] addSubview:_decoration]; + } + _outerCircleView = [[UIImageView alloc] initWithImage:[self outerCircleImage:self.pallete != nil ? self.pallete.buttonColor : TGAccentColor()]]; _outerCircleView.alpha = 0.0f; _outerCircleView.tag = 0x01f2bca; @@ -419,8 +427,10 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerIconWrapperView.transform = CGAffineTransformIdentity; _innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); + _decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _innerCircleView.alpha = 0.2f; _outerCircleView.alpha = 0.2f; + _decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _lockPanelWrapperView.transform = CGAffineTransformMakeTranslation(0.0f, 100.0f); _lockPanelWrapperView.alpha = 0.0f; @@ -429,6 +439,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius [UIView animateWithDuration:0.50 delay:0.0 usingSpringWithDamping:0.55f initialSpringVelocity:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{ _innerCircleView.transform = CGAffineTransformIdentity; _outerCircleView.transform = CGAffineTransformMakeScale(outerCircleMinScale, outerCircleMinScale); + _decoration.transform = CGAffineTransformIdentity; _lockPanelWrapperView.transform = CGAffineTransformIdentity; } completion:nil]; @@ -438,6 +449,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius self.iconView.alpha = 0.0f; _innerIconWrapperView.alpha = 1.0f; _outerCircleView.alpha = 1.0f; + _decoration.alpha = 1.0; _lockPanelWrapperView.alpha = 1.0f; }]; @@ -473,8 +485,10 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius [UIView animateWithDuration:0.18 animations:^{ _innerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _outerCircleView.transform = CGAffineTransformMakeScale(0.2f, 0.2f); + _decoration.transform = CGAffineTransformMakeScale(0.2f, 0.2f); _innerCircleView.alpha = 0.0f; _outerCircleView.alpha = 0.0f; + _decoration.alpha = 0.0f; self.iconView.alpha = 1.0f; _innerIconWrapperView.alpha = 0.0f; @@ -764,6 +778,9 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius } - (void)displayLinkUpdate { + if (_decoration != NULL) { + _outerCircleView.image = nil; + } NSTimeInterval t = CACurrentMediaTime(); if (t > _animationStartTime + 0.5) { _currentLevel = _currentLevel * 0.8f + _inputLevel * 0.2f; @@ -783,11 +800,14 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius transform = CGAffineTransformScale(translation, _currentScale, _currentScale); _innerCircleView.transform = transform; + + [_decoration tick:_currentLevel]; } } - (void)addMicLevel:(CGFloat)level { _inputLevel = level; + [_decoration updateLevel:level]; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer { diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index 086b2f4183..a56f0291a4 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -387,6 +387,10 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto return ChatTextInputMediaRecordingButtonPresenter(account: self.account!, presentController: self.presentController) } + func micButtonDecoration() -> (UIView & TGModernConversationInputMicButtonDecoration)! { + return CombinedWaveView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 640.0, height: 640.0)), color: self.theme.chat.inputPanel.actionControlFillColor) + } + private var previousSize = CGSize() func layoutItems() { let size = self.bounds.size diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 9e44967397..acb1d93031 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Display +import AsyncDisplayKit import Postbox import TelegramCore import SyncCore @@ -26,7 +27,7 @@ public final class TelegramRootController: NavigationController { private var permissionsDisposable: Disposable? private var presentationDataDisposable: Disposable? private var presentationData: PresentationData - + public init(context: AccountContext) { self.context = context @@ -120,21 +121,8 @@ public final class TelegramRootController: NavigationController { self.accountSettingsController = accountSettingsController self.rootTabController = tabBarController self.pushViewController(tabBarController, animated: false) - -// let _ = (archivedStickerPacks(account: self.context.account, namespace: .stickers) -// |> deliverOnMainQueue).start(next: { [weak self] stickerPacks in -// var packs: [(StickerPackCollectionInfo, StickerPackItem?)] = [] -// for pack in stickerPacks { -// packs.append((pack.info, pack.topItems.first)) -// } -// -// if let strongSelf = self { -// let controller = archivedStickersNoticeController(context: strongSelf.context, archivedStickerPacks: packs) -// strongSelf.chatListController?.present(controller, in: .window(.root)) -// } -// }) } - + public func updateRootControllers(showCallsTab: Bool) { guard let rootTabController = self.rootTabController else { return diff --git a/submodules/TelegramUI/Sources/WaveButtonNode.swift b/submodules/TelegramUI/Sources/WaveButtonNode.swift new file mode 100644 index 0000000000..6af4225cd3 --- /dev/null +++ b/submodules/TelegramUI/Sources/WaveButtonNode.swift @@ -0,0 +1,468 @@ +import Foundation +import UIKit +import Display +import LegacyComponents + +private struct Constants { + static let sineWaveSpeed: CGFloat = 0.81 + static let smallWaveRadius: CGFloat = 0.55 + static let smallWaveScale: CGFloat = 0.40 + static let smallWaveScaleSpeed: CGFloat = 0.6 + static let flingDistance: CGFloat = 0.5 + + static let circleRadius: CGFloat = 56.0 + + static let animationSpeed: CGFloat = 0.35 * 0.1 + static let animationSpeedSmall: CGFloat = 0.55 * 0.1 + + static let rotationSpeed: CGFloat = 0.36 * 0.1 + static let waveAngle: CGFloat = 0.03 + static let randomRadiusSize: CGFloat = 0.3 + + static let idleWaveAngle: CGFloat = 0.5 + static let idleScaleSpeed: CGFloat = 0.3 + static let idleRotationSpeed: CGFloat = 0.2 + static let idleRadiusValue: CGFloat = 0.56 + static let idleRotationDiff: CGFloat = 0.1 * idleRotationSpeed +} + +class CombinedWaveView: UIView, TGModernConversationInputMicButtonDecoration { + private let bigWaveView: WaveView + private let smallWaveView: WaveView + + private var level: CGFloat = 0.0 + + init(frame: CGRect, color: UIColor) { + let n = 12 + let bounds = CGRect(origin: CGPoint(), size: frame.size) + self.bigWaveView = WaveView(frame: bounds, n: n, amplitudeRadius: 40.0, isBig: true, color: color.withAlphaComponent(0.3)) + self.smallWaveView = WaveView(frame: bounds, n: n, amplitudeRadius: 35.0, isBig: false, color: color.withAlphaComponent(0.15)) + + super.init(frame: frame) + + self.isUserInteractionEnabled = false + + self.bigWaveView.rotation = CGFloat.pi / 6.0 + self.bigWaveView.amplitudeWaveDif = 0.02 * Constants.sineWaveSpeed * CGFloat.pi / 180.0 + + self.smallWaveView.amplitudeWaveDif = 0.026 * Constants.sineWaveSpeed + self.smallWaveView.amplitudeRadius = 20.0 + 20.0 * Constants.smallWaveRadius + self.smallWaveView.maxScale = 0.3 * Constants.smallWaveScale + self.smallWaveView.scaleSpeed = 0.001 * Constants.smallWaveScaleSpeed + self.smallWaveView.fling = Constants.flingDistance + + self.addSubview(self.bigWaveView) + self.addSubview(self.smallWaveView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateLevel(_ level: CGFloat) { + let level = level * 0.45 + self.level = level + self.bigWaveView.setLevel(level) + self.smallWaveView.setLevel(level) + } + + func tick(_ level: CGFloat) { + let radius = 56.0 + 30.0 * level * 0.45 + self.bigWaveView.tick(circleRadius: radius) + self.smallWaveView.tick(circleRadius: radius) + } +} + +class WaveView : UIView { + var fling: CGFloat = 0.0 + private var animateToAmplitude: CGFloat = 0.0 + private var amplitude: CGFloat = 0.0 + private var slowAmplitude: CGFloat = 0.0 + private var animateAmplitudeDiff: CGFloat = 0.0 + private var animateAmplitudeSlowDiff: CGFloat = 0.0 + + private var lastRadius: CGFloat = 0.0 + private var radiusDiff: CGFloat = 0.0 + private var waveDiff: CGFloat = 0.0 + private var waveAngle: CGFloat = 0.0 + + private var incRandomAdditionals = false + + var rotation: CGFloat = 0.0 + private var idleRotation: CGFloat = 0.0 + private var innerRotation: CGFloat = 0.0 + + var amplitudeWaveDif: CGFloat = 0.0 + + var amplitudeRadius: CGFloat + private let isBig: Bool + + private var idleRadius: CGFloat = 0.0 + private var idleRadiusK: CGFloat = 0.15 * Constants.idleWaveAngle + private var expandIdleRadius = false + private var expandScale = false + + private var isIdle = true + private var scale: CGFloat = 1.0 + private var scaleIdleDif: CGFloat = 0.0 + private var scaleDif: CGFloat = 0.0 + var scaleSpeed: CGFloat = 0.00008 + public var scaleSpeedIdle: CGFloat = 0.0002 * Constants.idleScaleSpeed + var maxScale: CGFloat = 0.0 + + private var flingRadius: CGFloat = 0.0 + + private let randomAdditions: CGFloat = 8.0 * Constants.randomRadiusSize + + private var idleGlobalRadius: CGFloat = 10.0 * Constants.idleRadiusValue + private var sineAngleMax: CGFloat = 0.0 + + private let n: Int + private let l: CGFloat + private var additions: [CGFloat] + + var idleStateDiff: CGFloat = 0.0 + var radius: CGFloat = 60.0; + var cubicBezierK: CGFloat = 1.0; + + var randomK: CGFloat = 0.0 + + var color: UIColor + + init(frame: CGRect, n: Int, amplitudeRadius: CGFloat, isBig: Bool, color: UIColor) { + self.n = n + self.amplitudeRadius = amplitudeRadius + self.isBig = isBig + self.color = color + + self.expandIdleRadius = isBig + self.radiusDiff = 34.0 * 0.0012 + + self.l = 4.0 / 3.0 * tan(CGFloat.pi / (2.0 * CGFloat(self.n))) + self.additions = Array(repeating: 0.0, count: self.n) + + super.init(frame: frame) + + self.backgroundColor = .clear + self.isOpaque = false + + self.updateAdditions() + } + + func setLevel(_ level: CGFloat) { + self.animateToAmplitude = level + + let amplitudeDelta: CGFloat + let amplitudeSlowDelta: CGFloat + if self.isBig { + if self.animateToAmplitude > self.amplitude { + amplitudeDelta = 300.0 * Constants.animationSpeed + amplitudeSlowDelta = 500.0 * Constants.animationSpeed + } else { + amplitudeDelta = 500.0 * Constants.animationSpeed + amplitudeSlowDelta = 500.0 * Constants.animationSpeed + } + } else { + if self.animateToAmplitude > self.amplitude { + amplitudeDelta = 400.0 * Constants.animationSpeedSmall + amplitudeSlowDelta = 500.0 * Constants.animationSpeedSmall + } else { + amplitudeDelta = 500.0 * Constants.animationSpeedSmall + amplitudeSlowDelta = 500.0 * Constants.animationSpeedSmall + } + } + + self.animateAmplitudeDiff = (self.animateToAmplitude - self.amplitude) / (100.0 + amplitudeDelta) + self.animateAmplitudeSlowDiff = (self.animateToAmplitude - self.slowAmplitude) / (100.0 + amplitudeSlowDelta) + + let isIdle = level < 0.1 + if self.isIdle != isIdle && isIdle && self.isBig { +// +// +// + } + + self.isIdle = isIdle + } + + private var wasFling = false + + private func startFling(delta: CGFloat) { + self.pop_removeAnimation(forKey: "fling1") + self.pop_removeAnimation(forKey: "fling2") + + let fling = self.fling * 2.0 + let flingDistance = delta * self.amplitudeRadius * (self.isBig ? 8.0 : 20.0) * 16.0 * fling + + let animation = POPBasicAnimation() + animation.property = POPAnimatableProperty.property(withName: "fling1", initializer: { property in + property?.readBlock = { node, values in + values?.pointee = (node as! WaveView).flingRadius + } + property?.writeBlock = { node, values in + (node as! WaveView).flingRadius = values!.pointee + } + property?.threshold = 0.01 + }) as? POPAnimatableProperty + animation.fromValue = self.flingRadius as NSNumber + animation.toValue = flingDistance as NSNumber + animation.duration = Double((self.isBig ? 0.2 : 0.35) * fling) + animation.completionBlock = { [weak self] _, finished in + guard let strongSelf = self else { + return + } + + let animation = POPBasicAnimation() + animation.property = POPAnimatableProperty.property(withName: "fling2", initializer: { property in + property?.readBlock = { node, values in + values?.pointee = (node as! WaveView).flingRadius + } + property?.writeBlock = { node, values in + (node as! WaveView).flingRadius = values!.pointee + } + property?.threshold = 0.01 + }) as? POPAnimatableProperty + animation.fromValue = flingDistance as NSNumber + animation.toValue = 0.0 as NSNumber + animation.duration = Double((strongSelf.isBig ? 0.22 : 0.38) * fling) + strongSelf.pop_add(animation, forKey: "fling2") + } + self.pop_add(animation, forKey: "fling1") + } + + private var lastUpdateTime: CGFloat? + func tick(circleRadius: CGFloat) { + let dt: CGFloat + let time = CGFloat(CACurrentMediaTime()) + if let lastUpdateTime = self.lastUpdateTime { + dt = (time - lastUpdateTime) * 1000.0 + } else { + dt = 0.0 + } + self.lastUpdateTime = time + + if self.animateToAmplitude != self.amplitude { + self.amplitude += self.animateAmplitudeDiff * dt + if self.animateAmplitudeDiff > 0.0 { + if self.amplitude > self.animateToAmplitude { + self.amplitude = self.animateToAmplitude + } + } else { + if self.amplitude < self.animateToAmplitude { + self.amplitude = self.animateToAmplitude + } + } + + if abs(self.amplitude - self.animateToAmplitude) * self.amplitudeRadius < 4.0 { + if !self.wasFling { + self.startFling(delta: self.animateAmplitudeDiff) + self.wasFling = true + } + } else { + self.wasFling = false + } + } + + if self.animateToAmplitude != self.slowAmplitude { + self.slowAmplitude += self.animateAmplitudeSlowDiff * dt + if abs(self.slowAmplitude - self.amplitude) > 0.2 { + self.slowAmplitude = self.amplitudeRadius + (self.slowAmplitude > self.amplitude ? 0.2 : -0.2) + } + if self.animateAmplitudeSlowDiff > 0.0 { + if self.slowAmplitude > self.animateToAmplitude { + self.slowAmplitude = self.animateToAmplitude + } + } else { + if self.slowAmplitude < self.animateToAmplitude { + self.slowAmplitude = self.animateToAmplitude + } + } + } + + self.idleRadius = circleRadius * self.idleRadiusK + if self.expandIdleRadius { + self.scaleIdleDif += self.scaleSpeedIdle * dt + if self.scaleIdleDif >= 0.05 { + self.scaleIdleDif = 0.05 + self.expandIdleRadius = false + } + } else { + self.scaleIdleDif -= self.scaleSpeedIdle * dt + if self.scaleIdleDif < 0.0 { + self.scaleIdleDif = 0.0 + self.expandIdleRadius = true + } + } + + if self.maxScale > 0.0 { + if self.expandScale { + self.scaleDif += self.scaleSpeed * dt + if self.scaleDif >= self.maxScale { + self.scaleDif = self.maxScale + self.expandScale = false + } + } else { + self.scaleDif -= self.scaleSpeed * dt + if self.scaleDif < 0.0 { + self.scaleDif = 0.0 + self.expandScale = true + } + } + } + + if self.sineAngleMax > self.animateToAmplitude { + self.sineAngleMax -= 0.25 + if self.sineAngleMax < self.animateToAmplitude { + self.sineAngleMax = self.animateToAmplitude + } + } else if self.sineAngleMax < self.animateToAmplitude { + self.sineAngleMax += 0.25 + if self.sineAngleMax > self.animateToAmplitude { + self.sineAngleMax = self.animateToAmplitude + } + } + + if !self.isIdle { + self.rotation += (Constants.rotationSpeed * 0.5 + Constants.rotationSpeed * 4.0 * (self.amplitude > 0.5 ? 1.0 : self.amplitude / 0.5) * dt) * CGFloat.pi / 180.0 + while self.rotation > CGFloat.pi * 2.0 { + self.rotation -= CGFloat.pi * 2.0 + } + } else { + self.idleRotation += Constants.idleRotationDiff * dt * CGFloat.pi / 180.0 + while self.idleRotation > CGFloat.pi * 2.0 { + self.idleRotation -= CGFloat.pi * 2.0 + } + } + + if self.lastRadius < circleRadius { + self.lastRadius = circleRadius + } else { + self.lastRadius -= self.radiusDiff * dt + if self.lastRadius < circleRadius { + self.lastRadius = circleRadius + } + } + + self.lastRadius = circleRadius + + if !self.isIdle { + self.waveAngle += self.amplitudeWaveDif * self.sineAngleMax * dt + if self.isBig { + self.waveDiff = cos(self.waveAngle) + } else { + self.waveDiff = -cos(self.waveAngle) + } + + if self.waveDiff > 0.0 && self.incRandomAdditionals { + self.updateAdditions() + self.incRandomAdditionals = false + } else if self.waveDiff < 0.0 && !self.incRandomAdditionals { + self.updateAdditions() + self.incRandomAdditionals = true + } + } + + self.prepareDraw() + } + + func updateAdditions() { + self.additions = (0..= self.n { + j = 0 + } + + r = ((j % 2 == 0) ? r1 : r2) + self.randomK * self.additions[j] + + var p3 = CGPoint(x: cx, y: cy - r) + var p4 = CGPoint(x: cx - l + self.randomK * self.additions[j] * self.l, y: cy - r) + + transform = CGAffineTransform.init(translationX: cx, y: cy) + transform = transform.rotated(by: 2 * CGFloat.pi / CGFloat(self.n) * CGFloat(j)) + transform = transform.translatedBy(x: -cx, y: -cy) + + p3 = p3.applying(transform) + p4 = p4.applying(transform) + + if i == 0 { + path.move(to: p1) + } + + path.addCurve(to: p3, controlPoint1: p2, controlPoint2: p4) + } + + ctx.setFillColor(self.color.cgColor) + + ctx.saveGState() + ctx.translateBy(x: rect.width / 2.0, y: rect.height / 2.0) + ctx.scaleBy(x: self.scale, y: self.scale) + ctx.rotate(by: self.innerRotation) + ctx.translateBy(x: -rect.width / 2.0, y: -rect.height / 2.0) + + ctx.addPath(path.cgPath) + ctx.drawPath(using: .fill) + ctx.restoreGState() + } +}