Various improvements

This commit is contained in:
Isaac 2025-05-30 21:08:32 +08:00
parent 0a1be88185
commit 4fdbe44825
12 changed files with 116 additions and 50 deletions

View File

@ -14383,7 +14383,7 @@ Sorry for the inconvenience.";
"ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE"; "ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE";
"ChannelMessages.PriceSectionFooter" = "You will receive 85% of the selected fee for each incoming message."; "ChannelMessages.PriceSectionFooter" = "You will receive 85% of the selected fee for each incoming message.";
"ChatList.MonoforumLabel" = "MESSAGES"; "ChatList.MonoforumLabel" = "DIRECT";
"ChatList.MonoforumEmptyText" = "No messages here yet..."; "ChatList.MonoforumEmptyText" = "No messages here yet...";
"Chat.InlineTopicMenu.Reorder" = "Reorder"; "Chat.InlineTopicMenu.Reorder" = "Reorder";
@ -14400,6 +14400,7 @@ Sorry for the inconvenience.";
"Chat.EmptyStateMonoforum.Text" = "Send a direct message to the administrator of **%@**."; "Chat.EmptyStateMonoforum.Text" = "Send a direct message to the administrator of **%@**.";
"Chat.EmptyStateMonoforumPaid.Text" = "**%1$@** charges **%2$@**\nper message to its admin."; "Chat.EmptyStateMonoforumPaid.Text" = "**%1$@** charges **%2$@**\nper message to its admin.";
"Chat.Monoforum.Subtitle" = "Direct messages";
"Monoforum.NameFormat" = "%@ Messages"; "Monoforum.NameFormat" = "%@ Messages";

View File

@ -97,22 +97,14 @@ open class TransformImageNode: ASDisplayNode {
self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in
let apply: () -> Void = { let apply: () -> Void = {
if let strongSelf = self { if let strongSelf = self {
var animateFromContents: Any?
if strongSelf.contents == nil { if strongSelf.contents == nil {
if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously { if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously {
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
} }
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) { } else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
let tempLayer = CALayer() animateFromContents = strongSelf.contents
if strongSelf.captureProtected {
setLayerDisableScreenshots(tempLayer, strongSelf.captureProtected)
}
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
tempLayer.contents = strongSelf.contents
strongSelf.layer.addSublayer(tempLayer)
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})
} }
var imageUpdate: UIImage? var imageUpdate: UIImage?
@ -129,6 +121,23 @@ open class TransformImageNode: ASDisplayNode {
if let imageUpdated = strongSelf.imageUpdated { if let imageUpdated = strongSelf.imageUpdated {
imageUpdated(imageUpdate) imageUpdated(imageUpdate)
} }
if let animateFromContents {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
transition.animateContents(layer: strongSelf.layer, from: animateFromContents)
/*let tempLayer = CALayer()
if strongSelf.captureProtected {
setLayerDisableScreenshots(tempLayer, strongSelf.captureProtected)
}
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
tempLayer.contents = animateFromContents
strongSelf.layer.addSublayer(tempLayer)
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
tempLayer?.removeFromSuperlayer()
})*/
}
} }
} }
if dispatchOnDisplayLink && !attemptSynchronously { if dispatchOnDisplayLink && !attemptSynchronously {
@ -168,6 +177,34 @@ open class TransformImageNode: ASDisplayNode {
} }
} }
public func asyncLayoutWithAnimation() -> (TransformImageArguments) -> ((ListViewItemUpdateAnimation) -> Void) {
let currentTransform = self.currentTransform
let currentArguments = self.currentArguments
return { [weak self] arguments in
let updatedImage: UIImage?
if currentArguments != arguments {
updatedImage = currentTransform?(arguments)?.generateImage()
} else {
updatedImage = nil
}
return { animation in
guard let self else {
return
}
if let image = updatedImage {
self.contents = image.cgImage
self.image = image
self.currentArguments = arguments
if let _ = self.overlayColor {
self.applyOverlayColor(animated: false)
}
}
self.argumentsPromise.set(arguments)
}
}
}
public class func asyncLayout(_ maybeNode: TransformImageNode?) -> (TransformImageArguments) -> (() -> TransformImageNode) { public class func asyncLayout(_ maybeNode: TransformImageNode?) -> (TransformImageArguments) -> (() -> TransformImageNode) {
return { arguments in return { arguments in
let node: TransformImageNode let node: TransformImageNode

View File

@ -402,7 +402,7 @@ public final class MediaPlayerNode: ASDisplayNode {
} }
} }
private func updateLayout() { public func updateLayout() {
let bounds = self.bounds let bounds = self.bounds
if bounds.isEmpty { if bounds.isEmpty {
return return

View File

@ -858,7 +858,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
let currentMessage = self.message let currentMessage = self.message
let currentMedia = self.media let currentMedia = self.media
let imageLayout = self.imageNode.asyncLayout() let imageLayout = self.imageNode.asyncLayoutWithAnimation()
let statusLayout = self.dateAndStatusNode.asyncLayout() let statusLayout = self.dateAndStatusNode.asyncLayout()
let currentVideoNode = self.videoNode let currentVideoNode = self.videoNode
@ -1894,7 +1894,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
timestampMaskView.image = strongSelf.generateTimestampMaskImage(corners: arguments.corners) timestampMaskView.image = strongSelf.generateTimestampMaskImage(corners: arguments.corners)
} }
strongSelf.currentImageArguments = arguments strongSelf.currentImageArguments = arguments
imageApply() imageApply(transition)
if let statusApply = statusApply { if let statusApply = statusApply {
let dateAndStatusFrame = CGRect(origin: CGPoint(x: cleanImageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: cleanImageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize) let dateAndStatusFrame = CGRect(origin: CGPoint(x: cleanImageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: cleanImageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
@ -2084,8 +2084,13 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
strongSelf.imageNode.cornerRadius = 0.0 strongSelf.imageNode.cornerRadius = 0.0
} }
if videoNode.bounds.isEmpty {
videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate) videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate)
videoNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size) videoNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
} else {
videoNode.updateLayout(size: arguments.drawingSize, transition: transition.transition)
transition.animator.updateFrame(layer: videoNode.layer, frame: CGRect(origin: CGPoint(), size: imageFrame.size), completion: nil)
}
if strongSelf.visibility && strongSelf.internallyVisible && !presentationData.isPreview { if strongSelf.visibility && strongSelf.internallyVisible && !presentationData.isPreview {
if !videoNode.canAttachContent { if !videoNode.canAttachContent {

View File

@ -1359,7 +1359,7 @@ public final class ChatSideTopicsPanel: Component {
maximumNumberOfLines: 2 maximumNumberOfLines: 2
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: 200.0, height: 200.0) containerSize: CGSize(width: 400.0, height: 200.0)
) )
let contentSize: CGFloat = leftInset + rightInset + titleSize.height let contentSize: CGFloat = leftInset + rightInset + titleSize.height

View File

@ -68,6 +68,8 @@ public enum ChatTitleContent: Equatable {
return false return false
} }
if lhs.peerPresences.count != rhs.peerPresences.count { if lhs.peerPresences.count != rhs.peerPresences.count {
return false
} else {
for (key, value) in lhs.peerPresences { for (key, value) in lhs.peerPresences {
if let rhsValue = rhs.peerPresences[key] { if let rhsValue = rhs.peerPresences[key] {
if !value.isEqual(to: rhsValue) { if !value.isEqual(to: rhsValue) {

View File

@ -397,18 +397,27 @@ extension ChatControllerImpl {
self.navigationActionDisposable.set((peerView.get() self.navigationActionDisposable.set((peerView.get()
|> take(1) |> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView in |> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
if let strongSelf = self, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { guard let self else {
return
}
guard var peer = peerView.peers[peerView.peerId] else {
return
}
if let channel = peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] {
peer = mainPeer
}
if peer.id == strongSelf.context.account.peerId { if peer.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) == nil && !self.presentationInterfaceState.isNotAccessible {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { if peer.id == self.context.account.peerId {
strongSelf.effectiveNavigationController?.pushViewController(infoController) if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) {
self.effectiveNavigationController?.pushViewController(infoController)
} }
} else { } else {
var expandAvatar = expandAvatar var expandAvatar = expandAvatar
if peer.smallProfileImage == nil { if peer.smallProfileImage == nil {
expandAvatar = false expandAvatar = false
} }
if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { if let validLayout = self.validLayout, validLayout.deviceMetrics.type == .tablet {
expandAvatar = false expandAvatar = false
} }
let mode: PeerInfoControllerMode let mode: PeerInfoControllerMode
@ -420,12 +429,12 @@ extension ChatControllerImpl {
default: default:
mode = .generic mode = .generic
} }
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: strongSelf.contentData?.inviteRequestsContext) { if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) {
strongSelf.effectiveNavigationController?.pushViewController(infoController) self.effectiveNavigationController?.pushViewController(infoController)
} }
} }
let _ = strongSelf.dismissPreviewing?(false) let _ = self.dismissPreviewing?(false)
} }
})) }))
case .replyThread: case .replyThread:

View File

@ -548,9 +548,9 @@ extension ChatControllerImpl {
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
} else if let channel = peer as? TelegramChannel, channel.isMonoForum { } else if let channel = peer as? TelegramChannel, channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] {
strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, "Direct messages", false) strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, strings.Chat_Monoforum_Subtitle, true)
} else { } else {
strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, false) strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, true)
} }
} else { } else {
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)

View File

@ -2553,16 +2553,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate) titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate)
} }
let topPanelTransition = ComponentTransition(transition) ComponentTransition(transition).setFrame(view: titleTopicsAccessoryPanelNode.view, frame: titleTopicsAccessoryPanelFrame)
/*switch topPanelTransition.animation { titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: 0.0, transition: ComponentTransition(transition))
case let .curve(duration, _):
topPanelTransition = topPanelTransition.withAnimation(.curve(duration: duration, curve: ComponentTransition.Animation.Curve(ChatMessageTransitionNodeImpl.verticalAnimationCurve)))
default:
break
}*/
topPanelTransition.setFrame(view: titleTopicsAccessoryPanelNode.view, frame: titleTopicsAccessoryPanelFrame)
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: 0.0, transition: topPanelTransition)
} else { } else {
let previousFrame = titleTopicsAccessoryPanelNode.frame let previousFrame = titleTopicsAccessoryPanelNode.frame
titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame

View File

@ -681,6 +681,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
private var loadedMessagesFromCachedDataDisposable: Disposable? private var loadedMessagesFromCachedDataDisposable: Disposable?
private var isSettingTopReplyThreadMessageShown: Bool = false
let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true) let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true)
private var topVisibleMessageRangeValueInitialized: Bool = false private var topVisibleMessageRangeValueInitialized: Bool = false
@ -3178,8 +3179,15 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
} }
} }
if !self.isSettingTopReplyThreadMessageShown {
self.isSettingTopReplyThreadMessageShown = true
self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue) self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue)
self.isSettingTopReplyThreadMessageShown = false
} else {
#if DEBUG
print("Ignore repeated isTopReplyThreadMessageShown update")
#endif
}
self.updateTopVisibleMessageRange(topVisibleMessageRange) self.updateTopVisibleMessageRange(topVisibleMessageRange)
let _ = self.visibleMessageRange.swap(topVisibleMessageRange.flatMap { range in let _ = self.visibleMessageRange.swap(topVisibleMessageRange.flatMap { range in
return VisibleMessageRange(lowerBound: range.lowerBound, upperBound: range.upperBound) return VisibleMessageRange(lowerBound: range.lowerBound, upperBound: range.upperBound)

View File

@ -1591,7 +1591,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
} }
} }
if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction), !isReplyThreadHead, !isMigrated { if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !(message.media.first is TelegramMediaAction), !isReplyThreadHead, !isMigrated {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
@ -1893,8 +1893,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
var canViewStats = false var canViewStats = false
var canViewAuthor = false var canViewAuthor = false
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let associatedPeerId = channel.associatedPeerId {
if message.effectivelyIncoming(context.account.peerId) { if message.effectivelyIncoming(context.account.peerId), message.author?.id == associatedPeerId {
canViewAuthor = true canViewAuthor = true
} }
} else if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden { } else if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden {

View File

@ -690,22 +690,34 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
if let dimensions = self.dimensions { if let dimensions = self.dimensions {
let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0))
let makeLayout = self.imageNode.asyncLayout() let makeLayout = self.imageNode.asyncLayoutWithAnimation()
let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.fileReference.media.isInstantVideo ? .clear : self.placeholderColor)) let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.fileReference.media.isInstantVideo ? .clear : self.placeholderColor))
applyLayout() let mappedAnimation: ListViewItemUpdateAnimation
if case let .animated(duration, curve) = transition {
mappedAnimation = .System(duration: duration, transition: ControlledTransition(duration: duration, curve: curve, interactive: false))
} else {
mappedAnimation = .None
}
applyLayout(mappedAnimation)
} }
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size))
let fromFrame = self.playerNode.frame let fromFrame = self.playerNode.frame
let toFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0) let toFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
if case let .animated(duration, curve) = transition, fromFrame != toFrame, !fromFrame.width.isZero, !fromFrame.height.isZero, !toFrame.width.isZero, !toFrame.height.isZero { if case let .animated(duration, curve) = transition, fromFrame != toFrame, !fromFrame.width.isZero, !fromFrame.height.isZero, !toFrame.width.isZero, !toFrame.height.isZero {
self.playerNode.frame = toFrame let _ = duration
transition.animatePosition(node: self.playerNode, from: CGPoint(x: fromFrame.center.x - toFrame.center.x, y: fromFrame.center.y - toFrame.center.y)) let _ = curve
self.playerNode.position = toFrame.center
self.playerNode.bounds = CGRect(origin: CGPoint(), size: toFrame.size)
self.playerNode.updateLayout()
transition.animatePosition(node: self.playerNode, from: CGPoint(x: fromFrame.center.x, y: fromFrame.center.y))
let transform = CATransform3DScale(CATransform3DIdentity, fromFrame.width / toFrame.width, fromFrame.height / toFrame.height, 1.0) let transform = CATransform3DScale(CATransform3DIdentity, fromFrame.width / toFrame.width, fromFrame.height / toFrame.height, 1.0)
self.playerNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration) self.playerNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration)
} else { } else {
transition.updateFrame(node: self.playerNode, frame: toFrame) transition.updatePosition(node: self.playerNode, position: toFrame.center)
transition.updateBounds(node: self.playerNode, bounds: CGRect(origin: CGPoint(), size: toFrame.size))
self.playerNode.updateLayout()
} }
if let thumbnailNode = self.thumbnailNode { if let thumbnailNode = self.thumbnailNode {
transition.updateFrame(node: thumbnailNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)) transition.updateFrame(node: thumbnailNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))