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)
|
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 {
|
if !transition.isAnimated {
|
||||||
navigationBar.layer.cancelAnimationsRecursive(key: "bounds")
|
navigationBar.layer.removeAnimation(forKey: "bounds")
|
||||||
navigationBar.layer.cancelAnimationsRecursive(key: "position")
|
navigationBar.layer.removeAnimation(forKey: "position")
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: navigationBar, frame: navigationBarFrame)
|
transition.updateFrame(node: navigationBar, frame: navigationBarFrame)
|
||||||
navigationBar.setHidden(!self.displayNavigationBar, animated: transition.isAnimated)
|
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) {
|
public func encode<T: Encodable>(_ value: T, forKey key: String) {
|
||||||
let innerEncoder = AdaptedPostboxEncoder()
|
let typeHash: Int32 = murMurHashString32("\(type(of: value))")
|
||||||
guard let innerData = try? innerEncoder.encode(value) else {
|
let innerEncoder = _AdaptedPostboxEncoder(typeHash: typeHash)
|
||||||
return
|
try! value.encode(to: innerEncoder)
|
||||||
}
|
|
||||||
|
|
||||||
self.encodeKey(key)
|
let (data, valueType) = innerEncoder.makeData()
|
||||||
var t: Int8 = ValueType.Object.rawValue
|
self.encodeInnerObjectData(data, valueType: valueType, forKey: key)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeInnerObjectData(_ value: Data, valueType: ValueType, forKey key: String) {
|
func encodeInnerObjectData(_ value: Data, valueType: ValueType, forKey key: String) {
|
||||||
@ -995,9 +985,10 @@ public final class PostboxDecoder {
|
|||||||
|
|
||||||
var length: Int32 = 0
|
var length: Int32 = 0
|
||||||
memcpy(&length, self.buffer.memory + self.offset, 4)
|
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()
|
let innerData = ReadBuffer(memory: self.buffer.memory + self.offset, length: Int(length), freeWhenDone: false).makeData()
|
||||||
self.offset += 4 + Int(length)
|
self.offset += Int(length)
|
||||||
|
|
||||||
return (innerData, actualValueType)
|
return (innerData, actualValueType)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import MurMurHash32
|
||||||
|
|
||||||
public class AdaptedPostboxEncoder {
|
public class AdaptedPostboxEncoder {
|
||||||
func encode(_ value: Encodable) throws -> Data {
|
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)
|
try value.encode(to: encoder)
|
||||||
return encoder.makeData().0
|
return encoder.makeData().0
|
||||||
}
|
}
|
||||||
@ -12,9 +15,15 @@ final class _AdaptedPostboxEncoder {
|
|||||||
var codingPath: [CodingKey] = []
|
var codingPath: [CodingKey] = []
|
||||||
|
|
||||||
var userInfo: [CodingUserInfoKey : Any] = [:]
|
var userInfo: [CodingUserInfoKey : Any] = [:]
|
||||||
|
|
||||||
|
let typeHash: Int32
|
||||||
|
|
||||||
fileprivate var container: AdaptedPostboxEncodingContainer?
|
fileprivate var container: AdaptedPostboxEncodingContainer?
|
||||||
|
|
||||||
|
init(typeHash: Int32) {
|
||||||
|
self.typeHash = typeHash
|
||||||
|
}
|
||||||
|
|
||||||
func makeData() -> (Data, ValueType) {
|
func makeData() -> (Data, ValueType) {
|
||||||
return self.container!.makeData()
|
return self.container!.makeData()
|
||||||
}
|
}
|
||||||
@ -27,8 +36,8 @@ extension _AdaptedPostboxEncoder: Encoder {
|
|||||||
|
|
||||||
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
|
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
|
||||||
assertCanCreateContainer()
|
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
|
self.container = container
|
||||||
|
|
||||||
return KeyedEncodingContainer(container)
|
return KeyedEncodingContainer(container)
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import MurMurHash32
|
||||||
|
|
||||||
extension _AdaptedPostboxEncoder {
|
extension _AdaptedPostboxEncoder {
|
||||||
final class KeyedContainer<Key> where Key: CodingKey {
|
final class KeyedContainer<Key> where Key: CodingKey {
|
||||||
var codingPath: [CodingKey]
|
var codingPath: [CodingKey]
|
||||||
var userInfo: [CodingUserInfoKey: Any]
|
var userInfo: [CodingUserInfoKey: Any]
|
||||||
|
let typeHash: Int32
|
||||||
|
|
||||||
let encoder: PostboxEncoder
|
let encoder: PostboxEncoder
|
||||||
|
|
||||||
init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
|
init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any], typeHash: Int32) {
|
||||||
self.codingPath = codingPath
|
self.codingPath = codingPath
|
||||||
self.userInfo = userInfo
|
self.userInfo = userInfo
|
||||||
|
self.typeHash = typeHash
|
||||||
|
|
||||||
self.encoder = PostboxEncoder()
|
self.encoder = PostboxEncoder()
|
||||||
}
|
}
|
||||||
@ -17,7 +20,7 @@ extension _AdaptedPostboxEncoder {
|
|||||||
func makeData() -> (Data, ValueType) {
|
func makeData() -> (Data, ValueType) {
|
||||||
let buffer = WriteBuffer()
|
let buffer = WriteBuffer()
|
||||||
|
|
||||||
var typeHash: Int32 = 0
|
var typeHash: Int32 = self.typeHash
|
||||||
buffer.write(&typeHash, offset: 0, length: 4)
|
buffer.write(&typeHash, offset: 0, length: 4)
|
||||||
|
|
||||||
let data = self.encoder.makeData()
|
let data = self.encoder.makeData()
|
||||||
@ -33,7 +36,8 @@ extension _AdaptedPostboxEncoder {
|
|||||||
|
|
||||||
extension _AdaptedPostboxEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
|
extension _AdaptedPostboxEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
|
||||||
func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
|
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)
|
try! value.encode(to: innerEncoder)
|
||||||
|
|
||||||
let (data, valueType) = innerEncoder.makeData()
|
let (data, valueType) = innerEncoder.makeData()
|
||||||
|
@ -106,17 +106,14 @@ extension _AdaptedPostboxEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func encode<T>(_ value: T) throws where T : Encodable {
|
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)
|
try! value.encode(to: innerEncoder)
|
||||||
|
|
||||||
let (data, _) = innerEncoder.makeData()
|
let (data, _) = innerEncoder.makeData()
|
||||||
|
|
||||||
let buffer = WriteBuffer()
|
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)
|
buffer.write(data)
|
||||||
|
|
||||||
|
@ -52,4 +52,33 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||||||
|
|
||||||
func onLayout() {
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let navigationController = strongSelf.effectiveNavigationController, let snapshotView = strongSelf.chatDisplayNode.view.snapshotView(afterScreenUpdates: false) {
|
if let navigationController = strongSelf.effectiveNavigationController {
|
||||||
snapshotView.frame = strongSelf.view.bounds
|
let snapshotState = strongSelf.chatDisplayNode.prepareSnapshotState(
|
||||||
strongSelf.view.addSubview(snapshotView)
|
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
|
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()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var storedAnimateFromSnapshotView: UIView?
|
private var storedAnimateFromSnapshotState: ChatControllerNode.SnapshotState?
|
||||||
|
|
||||||
private func animateFromPreviousController(snapshotView: UIView) {
|
private func animateFromPreviousController(snapshotState: ChatControllerNode.SnapshotState) {
|
||||||
self.storedAnimateFromSnapshotView = snapshotView
|
self.storedAnimateFromSnapshotState = snapshotState
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewWillAppear(_ animated: Bool) {
|
override public func viewWillAppear(_ animated: Bool) {
|
||||||
@ -7106,7 +7108,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
self.didAppear = true
|
self.didAppear = true
|
||||||
|
|
||||||
self.chatDisplayNode.historyNode.preloadPages = true
|
|
||||||
self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false
|
self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false
|
||||||
self.chatDisplayNode.historyNode.canReadHistory.set(combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in
|
self.chatDisplayNode.historyNode.canReadHistory.set(combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in
|
||||||
return a && b
|
return a && b
|
||||||
@ -7455,15 +7456,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if let snapshotView = self.storedAnimateFromSnapshotView {
|
if let snapshotState = self.storedAnimateFromSnapshotState {
|
||||||
self.storedAnimateFromSnapshotView = nil
|
self.storedAnimateFromSnapshotState = nil
|
||||||
|
|
||||||
snapshotView.frame = self.view.bounds.offsetBy(dx: 0.0, dy: -self.view.bounds.height)
|
if let titleViewSnapshotState = snapshotState.titleViewSnapshotState {
|
||||||
self.view.insertSubview(snapshotView, at: 0)
|
self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState)
|
||||||
|
}
|
||||||
self.view.layer.animateBoundsOriginYAdditive(from: -self.view.bounds.height, to: 0.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, completion: { [weak snapshotView] _ in
|
if let avatarSnapshotState = snapshotState.avatarSnapshotState {
|
||||||
snapshotView?.removeFromSuperview()
|
(self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshotState)
|
||||||
})
|
}
|
||||||
|
self.chatDisplayNode.animateFromSnapshot(snapshotState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2498,4 +2498,86 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return false
|
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
|
nextClientId += 1
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.clipsToBounds = false
|
||||||
|
|
||||||
self.accessibilityPageScrolledString = { [weak self] row, count in
|
self.accessibilityPageScrolledString = { [weak self] row, count in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -625,7 +627,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.preloadPages = false
|
self.preloadPages = true
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .bubbles:
|
case .bubbles:
|
||||||
self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
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)?
|
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)
|
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