mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Update unread channel navigation
This commit is contained in:
parent
15d1ba7193
commit
5b154aec67
@ -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)
|
||||
|
@ -585,22 +585,12 @@ public final class PostboxEncoder {
|
||||
}
|
||||
|
||||
public func encode<T: Encodable>(_ 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 {
|
||||
|
@ -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<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
|
||||
assertCanCreateContainer()
|
||||
|
||||
let container = KeyedContainer<Key>(codingPath: self.codingPath, userInfo: self.userInfo)
|
||||
|
||||
let container = KeyedContainer<Key>(codingPath: self.codingPath, userInfo: self.userInfo, typeHash: self.typeHash)
|
||||
self.container = container
|
||||
|
||||
return KeyedEncodingContainer(container)
|
||||
|
@ -1,15 +1,18 @@
|
||||
import Foundation
|
||||
import MurMurHash32
|
||||
|
||||
extension _AdaptedPostboxEncoder {
|
||||
final class KeyedContainer<Key> 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<T>(_ 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()
|
||||
|
@ -106,17 +106,14 @@ extension _AdaptedPostboxEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
|
||||
}
|
||||
|
||||
func encode<T>(_ 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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user