diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index b5b9e7cac5..3eb5c4492d 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -396,8 +396,8 @@ public enum TabBarItemContextActionType { } navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition) if !transition.isAnimated { - navigationBar.layer.cancelAnimationsRecursive(key: "bounds") - navigationBar.layer.cancelAnimationsRecursive(key: "position") + navigationBar.layer.removeAnimation(forKey: "bounds") + navigationBar.layer.removeAnimation(forKey: "position") } transition.updateFrame(node: navigationBar, frame: navigationBarFrame) navigationBar.setHidden(!self.displayNavigationBar, animated: transition.isAnimated) diff --git a/submodules/Postbox/Sources/Coding.swift b/submodules/Postbox/Sources/Coding.swift index 37a5c14d86..8a4f17e519 100644 --- a/submodules/Postbox/Sources/Coding.swift +++ b/submodules/Postbox/Sources/Coding.swift @@ -585,22 +585,12 @@ public final class PostboxEncoder { } public func encode(_ value: T, forKey key: String) { - let innerEncoder = AdaptedPostboxEncoder() - guard let innerData = try? innerEncoder.encode(value) else { - return - } + let typeHash: Int32 = murMurHashString32("\(type(of: value))") + let innerEncoder = _AdaptedPostboxEncoder(typeHash: typeHash) + try! value.encode(to: innerEncoder) - self.encodeKey(key) - var t: Int8 = ValueType.Object.rawValue - self.buffer.write(&t, offset: 0, length: 1) - - let string = "\(type(of: value))" - var typeHash: Int32 = murMurHashString32(string) - self.buffer.write(&typeHash, offset: 0, length: 4) - - var length: Int32 = Int32(innerData.count) - self.buffer.write(&length, offset: 0, length: 4) - self.buffer.write(innerData) + let (data, valueType) = innerEncoder.makeData() + self.encodeInnerObjectData(data, valueType: valueType, forKey: key) } func encodeInnerObjectData(_ value: Data, valueType: ValueType, forKey key: String) { @@ -995,9 +985,10 @@ public final class PostboxDecoder { var length: Int32 = 0 memcpy(&length, self.buffer.memory + self.offset, 4) + self.offset += 4 - let innerData = ReadBuffer(memory: self.buffer.memory + (self.offset + 4), length: Int(length), freeWhenDone: false).makeData() - self.offset += 4 + Int(length) + let innerData = ReadBuffer(memory: self.buffer.memory + self.offset, length: Int(length), freeWhenDone: false).makeData() + self.offset += Int(length) return (innerData, actualValueType) } else { diff --git a/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxEncoder.swift b/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxEncoder.swift index fefd6bb851..14bf249f28 100644 --- a/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxEncoder.swift +++ b/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxEncoder.swift @@ -1,8 +1,11 @@ import Foundation +import MurMurHash32 public class AdaptedPostboxEncoder { func encode(_ value: Encodable) throws -> Data { - let encoder = _AdaptedPostboxEncoder() + let typeHash: Int32 = murMurHashString32("\(type(of: value))") + + let encoder = _AdaptedPostboxEncoder(typeHash: typeHash) try value.encode(to: encoder) return encoder.makeData().0 } @@ -12,9 +15,15 @@ final class _AdaptedPostboxEncoder { var codingPath: [CodingKey] = [] var userInfo: [CodingUserInfoKey : Any] = [:] + + let typeHash: Int32 fileprivate var container: AdaptedPostboxEncodingContainer? + init(typeHash: Int32) { + self.typeHash = typeHash + } + func makeData() -> (Data, ValueType) { return self.container!.makeData() } @@ -27,8 +36,8 @@ extension _AdaptedPostboxEncoder: Encoder { func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { assertCanCreateContainer() - - let container = KeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo) + + let container = KeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo, typeHash: self.typeHash) self.container = container return KeyedEncodingContainer(container) diff --git a/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxKeyedEncodingContainer.swift b/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxKeyedEncodingContainer.swift index cd840a9580..8d0401ceb1 100644 --- a/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxKeyedEncodingContainer.swift +++ b/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxKeyedEncodingContainer.swift @@ -1,15 +1,18 @@ import Foundation +import MurMurHash32 extension _AdaptedPostboxEncoder { final class KeyedContainer where Key: CodingKey { var codingPath: [CodingKey] var userInfo: [CodingUserInfoKey: Any] + let typeHash: Int32 let encoder: PostboxEncoder - init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { + init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any], typeHash: Int32) { self.codingPath = codingPath self.userInfo = userInfo + self.typeHash = typeHash self.encoder = PostboxEncoder() } @@ -17,7 +20,7 @@ extension _AdaptedPostboxEncoder { func makeData() -> (Data, ValueType) { let buffer = WriteBuffer() - var typeHash: Int32 = 0 + var typeHash: Int32 = self.typeHash buffer.write(&typeHash, offset: 0, length: 4) let data = self.encoder.makeData() @@ -33,7 +36,8 @@ extension _AdaptedPostboxEncoder { extension _AdaptedPostboxEncoder.KeyedContainer: KeyedEncodingContainerProtocol { func encode(_ value: T, forKey key: Key) throws where T : Encodable { - let innerEncoder = _AdaptedPostboxEncoder() + let typeHash: Int32 = murMurHashString32("\(type(of: value))") + let innerEncoder = _AdaptedPostboxEncoder(typeHash: typeHash) try! value.encode(to: innerEncoder) let (data, valueType) = innerEncoder.makeData() diff --git a/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxUnkeyedEncodingContainer.swift b/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxUnkeyedEncodingContainer.swift index c546133b44..8efa257466 100644 --- a/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxUnkeyedEncodingContainer.swift +++ b/submodules/Postbox/Sources/Utils/Encoder/AdaptedPostboxUnkeyedEncodingContainer.swift @@ -106,17 +106,14 @@ extension _AdaptedPostboxEncoder.UnkeyedContainer: UnkeyedEncodingContainer { } func encode(_ value: T) throws where T : Encodable { - let innerEncoder = _AdaptedPostboxEncoder() + let typeHash: Int32 = murMurHashString32("\(type(of: value))") + + let innerEncoder = _AdaptedPostboxEncoder(typeHash: typeHash) try! value.encode(to: innerEncoder) let (data, _) = innerEncoder.makeData() let buffer = WriteBuffer() - var typeHash: Int32 = murMurHashString32("\(type(of: value))") - buffer.write(&typeHash, offset: 0, length: 4) - - var length: Int32 = Int32(data.count) - buffer.write(&length, offset: 0, length: 4) buffer.write(data) diff --git a/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift index c26dcc45f3..48169b0556 100644 --- a/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift @@ -52,4 +52,33 @@ final class ChatAvatarNavigationNode: ASDisplayNode { func onLayout() { } + + final class SnapshotState { + fileprivate let snapshotView: UIView + + fileprivate init(snapshotView: UIView) { + self.snapshotView = snapshotView + } + } + + func prepareSnapshotState() -> SnapshotState { + let snapshotView = self.avatarNode.view.snapshotView(afterScreenUpdates: false)! + return SnapshotState( + snapshotView: snapshotView + ) + } + + func animateFromSnapshot(_ snapshotState: SnapshotState) { + self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true) + + snapshotState.snapshotView.frame = self.frame + self.containerNode.view.addSubview(snapshotState.snapshotView) + + let snapshotView = snapshotState.snapshotView + snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 43e2d04645..dbfac8f4af 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -7036,11 +7036,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - if let navigationController = strongSelf.effectiveNavigationController, let snapshotView = strongSelf.chatDisplayNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = strongSelf.view.bounds - strongSelf.view.addSubview(snapshotView) + if let navigationController = strongSelf.effectiveNavigationController { + let snapshotState = strongSelf.chatDisplayNode.prepareSnapshotState( + titleViewSnapshotState: strongSelf.chatTitleView?.prepareSnapshotState(), + avatarSnapshotState: (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.prepareSnapshotState() + ) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: false, completion: { nextController in - (nextController as! ChatControllerImpl).animateFromPreviousController(snapshotView: snapshotView) + (nextController as! ChatControllerImpl).animateFromPreviousController(snapshotState: snapshotState) })) } } @@ -7048,10 +7050,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.displayNodeDidLoad() } - private var storedAnimateFromSnapshotView: UIView? + private var storedAnimateFromSnapshotState: ChatControllerNode.SnapshotState? - private func animateFromPreviousController(snapshotView: UIView) { - self.storedAnimateFromSnapshotView = snapshotView + private func animateFromPreviousController(snapshotState: ChatControllerNode.SnapshotState) { + self.storedAnimateFromSnapshotState = snapshotState } override public func viewWillAppear(_ animated: Bool) { @@ -7106,7 +7108,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.didAppear = true - self.chatDisplayNode.historyNode.preloadPages = true self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false self.chatDisplayNode.historyNode.canReadHistory.set(combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in return a && b @@ -7455,15 +7456,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - if let snapshotView = self.storedAnimateFromSnapshotView { - self.storedAnimateFromSnapshotView = nil + if let snapshotState = self.storedAnimateFromSnapshotState { + self.storedAnimateFromSnapshotState = nil - snapshotView.frame = self.view.bounds.offsetBy(dx: 0.0, dy: -self.view.bounds.height) - self.view.insertSubview(snapshotView, at: 0) - - self.view.layer.animateBoundsOriginYAdditive(from: -self.view.bounds.height, to: 0.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) + if let titleViewSnapshotState = snapshotState.titleViewSnapshotState { + self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState) + } + if let avatarSnapshotState = snapshotState.avatarSnapshotState { + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshotState) + } + self.chatDisplayNode.animateFromSnapshot(snapshotState) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index d5b569a1dd..34fc8fcffc 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2498,4 +2498,86 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { return false } } + + final class SnapshotState { + fileprivate let historySnapshotState: ChatHistoryListNode.SnapshotState + let titleViewSnapshotState: ChatTitleView.SnapshotState? + let avatarSnapshotState: ChatAvatarNavigationNode.SnapshotState? + let navigationButtonsSnapshotState: ChatHistoryNavigationButtons.SnapshotState + let titleAccessoryPanelSnapshot: UIView? + let navigationBarHeight: CGFloat + + fileprivate init( + historySnapshotState: ChatHistoryListNode.SnapshotState, + titleViewSnapshotState: ChatTitleView.SnapshotState?, + avatarSnapshotState: ChatAvatarNavigationNode.SnapshotState?, + navigationButtonsSnapshotState: ChatHistoryNavigationButtons.SnapshotState, + titleAccessoryPanelSnapshot: UIView?, + navigationBarHeight: CGFloat + ) { + self.historySnapshotState = historySnapshotState + self.titleViewSnapshotState = titleViewSnapshotState + self.avatarSnapshotState = avatarSnapshotState + self.navigationButtonsSnapshotState = navigationButtonsSnapshotState + self.titleAccessoryPanelSnapshot = titleAccessoryPanelSnapshot + self.navigationBarHeight = navigationBarHeight + } + } + + func prepareSnapshotState( + titleViewSnapshotState: ChatTitleView.SnapshotState?, + avatarSnapshotState: ChatAvatarNavigationNode.SnapshotState? + ) -> SnapshotState { + var titleAccessoryPanelSnapshot: UIView? + if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let snapshot = titleAccessoryPanelNode.view.snapshotView(afterScreenUpdates: false) { + snapshot.frame = titleAccessoryPanelNode.frame + titleAccessoryPanelSnapshot = snapshot + } + return SnapshotState( + historySnapshotState: self.historyNode.prepareSnapshotState(), + titleViewSnapshotState: titleViewSnapshotState, + avatarSnapshotState: avatarSnapshotState, + navigationButtonsSnapshotState: self.navigateButtons.prepareSnapshotState(), + titleAccessoryPanelSnapshot: titleAccessoryPanelSnapshot, + navigationBarHeight: self.navigationBar?.backgroundNode.bounds.height ?? 0.0 + ) + } + + func animateFromSnapshot(_ snapshotState: SnapshotState) { + self.historyNode.animateFromSnapshot(snapshotState.historySnapshotState) + self.navigateButtons.animateFromSnapshot(snapshotState.navigationButtonsSnapshotState) + + if let titleAccessoryPanelSnapshot = snapshotState.titleAccessoryPanelSnapshot { + self.titleAccessoryPanelContainer.view.addSubview(titleAccessoryPanelSnapshot) + if let _ = self.titleAccessoryPanelNode { + titleAccessoryPanelSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak titleAccessoryPanelSnapshot] _ in + titleAccessoryPanelSnapshot?.removeFromSuperview() + }) + titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -10.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + } else { + titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -titleAccessoryPanelSnapshot.bounds.height), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak titleAccessoryPanelSnapshot] _ in + titleAccessoryPanelSnapshot?.removeFromSuperview() + }) + } + } + + if let titleAccessoryPanelNode = self.titleAccessoryPanelNode { + if let _ = snapshotState.titleAccessoryPanelSnapshot { + titleAccessoryPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) + titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: true) + } else { + titleAccessoryPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -titleAccessoryPanelNode.bounds.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) + } + } + + if let navigationBar = self.navigationBar { + let currentFrame = navigationBar.backgroundNode.frame + var previousFrame = currentFrame + previousFrame.size.height = snapshotState.navigationBarHeight + if previousFrame != currentFrame { + navigationBar.backgroundNode.update(size: previousFrame.size, transition: .immediate) + navigationBar.backgroundNode.update(size: currentFrame.size, transition: .animated(duration: 0.5, curve: .spring)) + } + } + } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index bcbf083594..828771efac 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -585,6 +585,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { nextClientId += 1 super.init() + + self.clipsToBounds = false self.accessibilityPageScrolledString = { [weak self] row, count in if let strongSelf = self { @@ -625,7 +627,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } - self.preloadPages = false + self.preloadPages = true switch self.mode { case .bubbles: self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0) @@ -2407,4 +2409,76 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } var animationCorrelationMessageFound: ((ChatMessageItemView, Int64?) -> Void)? + + final class SnapshotState { + fileprivate let snapshotTopInset: CGFloat + fileprivate let snapshotBottomInset: CGFloat + fileprivate let snapshotView: UIView + + fileprivate init( + snapshotTopInset: CGFloat, + snapshotBottomInset: CGFloat, + snapshotView: UIView + ) { + self.snapshotTopInset = snapshotTopInset + self.snapshotBottomInset = snapshotBottomInset + self.snapshotView = snapshotView + } + } + + func prepareSnapshotState() -> SnapshotState { + var snapshotTopInset: CGFloat = 0.0 + var snapshotBottomInset: CGFloat = 0.0 + self.forEachItemNode { itemNode in + let topOverflow = itemNode.frame.maxY - self.bounds.height + snapshotTopInset = max(snapshotTopInset, topOverflow) + + if itemNode.frame.minY < 0.0 { + snapshotBottomInset = max(snapshotBottomInset, -itemNode.frame.minY) + } + } + let snapshotView = self.view.snapshotView(afterScreenUpdates: false)! + + let currentSnapshotView = self.view.snapshotView(afterScreenUpdates: false)! + currentSnapshotView.frame = self.view.bounds + if let sublayers = self.layer.sublayers { + for sublayer in sublayers { + sublayer.isHidden = true + } + } + self.view.addSubview(currentSnapshotView) + + return SnapshotState( + snapshotTopInset: snapshotTopInset, + snapshotBottomInset: snapshotBottomInset, + snapshotView: snapshotView + ) + } + + func animateFromSnapshot(_ snapshotState: SnapshotState) { + var snapshotTopInset: CGFloat = 0.0 + var snapshotBottomInset: CGFloat = 0.0 + self.forEachItemNode { itemNode in + let topOverflow = itemNode.frame.maxY - self.bounds.height + snapshotTopInset = max(snapshotTopInset, topOverflow) + + if itemNode.frame.minY < 0.0 { + snapshotBottomInset = max(snapshotBottomInset, -itemNode.frame.minY) + } + } + + let snapshotParentView = UIView() + snapshotParentView.addSubview(snapshotState.snapshotView) + snapshotParentView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0) + snapshotParentView.frame = self.view.frame + + snapshotState.snapshotView.frame = snapshotParentView.bounds + self.view.superview?.insertSubview(snapshotParentView, belowSubview: self.view) + + snapshotParentView.layer.animatePosition(from: CGPoint(x: 0.0, y: 0.0), to: CGPoint(x: 0.0, y: -self.view.bounds.height - snapshotState.snapshotBottomInset - snapshotTopInset), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak snapshotParentView] _ in + snapshotParentView?.removeFromSuperview() + }) + + self.view.layer.animatePosition(from: CGPoint(x: 0.0, y: self.view.bounds.height + snapshotTopInset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) + } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift index 888f2dcfe1..77538962d5 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift @@ -163,4 +163,34 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } } } + + final class SnapshotState { + fileprivate let downButtonSnapshotView: UIView? + + fileprivate init( + downButtonSnapshotView: UIView? + ) { + self.downButtonSnapshotView = downButtonSnapshotView + } + } + + func prepareSnapshotState() -> SnapshotState { + var downButtonSnapshotView: UIView? + if !self.downButton.isHidden { + downButtonSnapshotView = self.downButton.view.snapshotView(afterScreenUpdates: false)! + } + return SnapshotState( + downButtonSnapshotView: downButtonSnapshotView + ) + } + + func animateFromSnapshot(_ snapshotState: SnapshotState) { + if self.downButton.isHidden != (snapshotState.downButtonSnapshotView == nil) { + if self.downButton.isHidden { + } else { + self.downButton.layer.animateAlpha(from: 0.0, to: self.downButton.alpha, duration: 0.3) + self.downButton.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true) + } + } + } } diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 7986280ed6..20e097103e 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -736,4 +736,33 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } return super.hitTest(point, with: event) } + + final class SnapshotState { + fileprivate let snapshotView: UIView + + fileprivate init(snapshotView: UIView) { + self.snapshotView = snapshotView + } + } + + func prepareSnapshotState() -> SnapshotState { + let snapshotView = self.snapshotView(afterScreenUpdates: false)! + return SnapshotState( + snapshotView: snapshotView + ) + } + + func animateFromSnapshot(_ snapshotState: SnapshotState) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.layer.animatePosition(from: CGPoint(x: 0.0, y: 20.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) + + snapshotState.snapshotView.frame = self.frame + self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self) + + let snapshotView = snapshotState.snapshotView + snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + } }