From c264378b6ad07cb37fc4d7cc7ca24d57c66e0c5f Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 23 May 2025 00:37:21 +0800 Subject: [PATCH] Monoforums --- submodules/Display/Source/NavigationBar.swift | 60 +++---- .../Display/Source/NavigationButtonNode.swift | 40 ++++- .../Sources/ChatAvatarNavigationNode.swift | 30 +++- .../ChatTitleView/Sources/ChatTitleView.swift | 12 +- .../Chat/ChatControllerLoadDisplayNode.swift | 39 ++++- ...UpdateChatPresentationInterfaceState.swift | 17 -- .../TelegramUI/Sources/ChatController.swift | 102 +----------- .../Sources/ChatControllerContentData.swift | 149 ++++++++++++++---- .../ChatInviteRequestsTitlePanelNode.swift | 57 +++++-- 9 files changed, 283 insertions(+), 223 deletions(-) diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 0c381ea128..32392fb45d 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -881,16 +881,6 @@ open class NavigationBar: ASDisplayNode { if needsLeftButton { if animated { - if self.leftButtonNode.view.superview != nil { - if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { - snapshotView.frame = self.leftButtonNode.frame - self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - if self.backButtonNode.view.superview != nil { if let snapshotView = self.backButtonNode.view.snapshotContentTree() { snapshotView.frame = self.backButtonNode.frame @@ -927,9 +917,11 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.removeFromSupernode() if let leftBarButtonItem = item.leftBarButtonItem { - self.leftButtonNode.updateItems([leftBarButtonItem]) + self.leftButtonNode.updateItems([], animated: animated) + self.leftButtonNode.updateItems([leftBarButtonItem], animated: animated) } else { - self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)]) + self.leftButtonNode.updateItems([], animated: animated) + self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)], animated: animated) } if self.leftButtonNode.supernode == nil { @@ -994,9 +986,6 @@ open class NavigationBar: ASDisplayNode { } self.updateAccessibilityElements() - if animated { - self.hintAnimateTitleNodeOnNextLayout = true - } } private func updateRightButton(animated: Bool) { @@ -1008,23 +997,14 @@ open class NavigationBar: ASDisplayNode { items = [rightBarButtonItem] } + self.rightButtonNodeUpdated = true + if !items.isEmpty { - if animated, self.rightButtonNode.view.superview != nil { - if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { - snapshotView.frame = self.rightButtonNode.frame - self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - self.rightButtonNode.updateItems(items) + self.rightButtonNode.updateItems([], animated: animated) + self.rightButtonNode.updateItems(items, animated: animated) if self.rightButtonNode.supernode == nil { self.buttonsContainerNode.addSubnode(self.rightButtonNode) } - if animated { - self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } } else { if animated, self.rightButtonNode.view.superview != nil { if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { @@ -1050,9 +1030,6 @@ open class NavigationBar: ASDisplayNode { self.rightButtonNode.removeFromSupernode() } - if animated { - self.hintAnimateTitleNodeOnNextLayout = true - } self.updateAccessibilityElements() } @@ -1062,6 +1039,7 @@ open class NavigationBar: ASDisplayNode { public let backButtonArrow: ASImageNode public let leftButtonNode: NavigationButtonNode public let rightButtonNode: NavigationButtonNode + private var rightButtonNodeUpdated: Bool = false public let additionalContentNode: SparseNode private let navigationBackgroundCutoutView: NavigationBackgroundCutoutView @@ -1359,7 +1337,7 @@ open class NavigationBar: ASDisplayNode { var leftTitleInset: CGFloat = leftInset + 1.0 var rightTitleInset: CGFloat = rightInset + 1.0 if self.backButtonNode.supernode != nil { - let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) + let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true) leftTitleInset = backButtonSize.width + backButtonInset + 1.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 @@ -1411,7 +1389,7 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.alpha = 1.0 } } else if self.leftButtonNode.supernode != nil { - let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) + let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true) leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0 var transition = transition @@ -1428,16 +1406,18 @@ open class NavigationBar: ASDisplayNode { transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 16.0, dy: 2.0), size: badgeSize)) if self.rightButtonNode.supernode != nil { - let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape) + let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape, isLeftAligned: false) rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0 self.rightButtonNode.alpha = 1.0 var transition = transition - if self.rightButtonNode.frame.width.isZero { + if self.rightButtonNode.frame.width.isZero || self.rightButtonNodeUpdated { transition = .immediate } + transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)) } + self.rightButtonNodeUpdated = false if let transitionState = self.transitionState { let progress = transitionState.progress @@ -1447,7 +1427,7 @@ open class NavigationBar: ASDisplayNode { break case .bottom: if let transitionBackButtonNode = self.transitionBackButtonNode { - let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape) + let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true) let initialX: CGFloat = backButtonInset + size.width * 0.3 let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0) @@ -1592,7 +1572,7 @@ open class NavigationBar: ASDisplayNode { node.updateManualText(self.backButtonNode.manualText) node.color = accentColor if let validLayout = self.validLayout { - let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape) + let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: true) node.frame = self.backButtonNode.frame } return node @@ -1608,7 +1588,7 @@ open class NavigationBar: ASDisplayNode { node.updateManualText(self.backButtonNode.manualText) node.color = accentColor if let validLayout = self.validLayout { - let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape) + let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: true) node.frame = self.backButtonNode.frame } return node.view @@ -1628,10 +1608,10 @@ open class NavigationBar: ASDisplayNode { items = [rightBarButtonItem] } } - node.updateItems(items) + node.updateItems(items, animated: false) node.color = accentColor if let validLayout = self.validLayout { - let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape) + let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: false) node.frame = self.backButtonNode.frame } return node diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index ea05895c76..73d49fc855 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -331,6 +331,8 @@ private final class NavigationButtonItemNode: ImmediateTextNode { public final class NavigationButtonNode: ContextControllerSourceNode { private var nodes: [NavigationButtonItemNode] = [] + private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = [] + public var singleCustomNode: ASDisplayNode? { for node in self.nodes { return node.node @@ -452,7 +454,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode { } } - func updateItems(_ items: [UIBarButtonItem]) { + func updateItems(_ items: [UIBarButtonItem], animated: Bool) { for i in 0 ..< items.count { let node: NavigationButtonItemNode if self.nodes.count > i { @@ -486,16 +488,41 @@ public final class NavigationButtonNode: ContextControllerSourceNode { node.bold = items[i].style == .done node.isEnabled = items[i].isEnabled node.node = items[i].customDisplayNode + + if animated { + node.layer.animateAlpha(from: 0.0, to: self.manualAlpha, duration: 0.16) + node.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + } } if items.count < self.nodes.count { for i in items.count ..< self.nodes.count { - self.nodes[i].removeFromSupernode() + let itemNode = self.nodes[i] + if animated { + disappearingNodes.append((itemNode.frame, self.bounds.size, itemNode)) + itemNode.layer.animateAlpha(from: self.manualAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak itemNode] _ in + guard let itemNode else { + return + } + + itemNode.removeFromSupernode() + + guard let self else { + return + } + if let index = self.disappearingNodes.firstIndex(where: { $0.node === itemNode }) { + self.disappearingNodes.remove(at: index) + } + }) + itemNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } else { + itemNode.removeFromSupernode() + } } self.nodes.removeSubrange(items.count...) } } - public func updateLayout(constrainedSize: CGSize, isLandscape: Bool) -> CGSize { + public func updateLayout(constrainedSize: CGSize, isLandscape: Bool, isLeftAligned: Bool) -> CGSize { var nodeOrigin = CGPoint() var totalHeight: CGFloat = 0.0 for i in 0 ..< self.nodes.count { @@ -520,6 +547,13 @@ public final class NavigationButtonNode: ContextControllerSourceNode { nodeOrigin.x -= 5.0 } } + + if !isLeftAligned { + for disappearingNode in self.disappearingNodes { + disappearingNode.node.frame = disappearingNode.frame.offsetBy(dx: nodeOrigin.x - disappearingNode.size.width, dy: (totalHeight - disappearingNode.size.height) * 0.5) + } + } + return CGSize(width: nodeOrigin.x, height: totalHeight) } diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift index 865e0b55ac..37a7531905 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift @@ -265,31 +265,47 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { public final class SnapshotState { fileprivate let snapshotView: UIView? + fileprivate let snapshotStatusView: UIView? - fileprivate init(snapshotView: UIView?) { + fileprivate init(snapshotView: UIView?, snapshotStatusView: UIView?) { self.snapshotView = snapshotView + self.snapshotStatusView = snapshotStatusView } } public func prepareSnapshotState() -> SnapshotState { let snapshotView = self.avatarNode.view.snapshotView(afterScreenUpdates: false) + let snapshotStatusView = self.statusView.view?.snapshotView(afterScreenUpdates: false) return SnapshotState( - snapshotView: snapshotView + snapshotView: snapshotView, + snapshotStatusView: snapshotStatusView ) } public 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) + self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16) + self.avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true) + + self.statusView.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16) + self.statusView.view?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true) if let snapshotView = snapshotState.snapshotView { snapshotView.frame = self.frame - self.containerNode.view.addSubview(snapshotView) + self.containerNode.view.insertSubview(snapshotView, at: 0) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) - snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } + if let snapshotStatusView = snapshotState.snapshotStatusView { + snapshotStatusView.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - snapshotStatusView.bounds.width) / 2.0), y: floor((self.containerNode.bounds.height - snapshotStatusView.bounds.height) / 2.0)), size: snapshotStatusView.bounds.size) + self.containerNode.view.insertSubview(snapshotStatusView, at: 0) + + snapshotStatusView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotStatusView] _ in + snapshotStatusView?.removeFromSuperview() + }) + snapshotStatusView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index ed7c2cf28b..5a8b4b2d42 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -1021,16 +1021,16 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { if activitySize.width < size.width { activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0) } - self.activityNode.frame = activityFrame + titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: activityFrame) } if let image = self.titleLeftIconNode.image { - self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size) + titleTransition.updateFrame(node: self.titleLeftIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)) } var nextIconX: CGFloat = titleFrame.width - self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) + titleTransition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize)) self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) nextIconX -= titleCredibilitySize.width @@ -1056,7 +1056,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame) titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + statusIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) + titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + statusIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)) if let image = self.titleLeftIconNode.image { self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size) @@ -1069,11 +1069,11 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) nextIconX -= titleCredibilitySize.width - self.titleStatusIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize) + titleTransition.updateFrame(view: self.titleStatusIconView, frame: CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize)) nextIconX -= titleStatusSize.width if let image = self.titleRightIconNode.image { - self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size) + titleTransition.updateFrame(node: self.titleRightIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size)) } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 81fb4d4912..d2d303b741 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -153,7 +153,8 @@ extension ChatControllerImpl { currentChatListFilter: self.currentChatListFilter, customChatNavigationStack: self.customChatNavigationStack, presentationData: self.presentationData, - historyNode: historyNode + historyNode: historyNode, + inviteRequestsContext: self.contentData?.inviteRequestsContext ) self.pendingContentData = (contentData, historyNode) self.contentDataDisposable = (contentData.isReady.get() @@ -248,7 +249,16 @@ extension ChatControllerImpl { self.chatDisplayNode.overlayTitle = contentData.overlayTitle - self.chatDisplayNode.historyNode.nextChannelToRead = contentData.state.nextChannelToRead + self.chatDisplayNode.historyNode.nextChannelToRead = contentData.state.nextChannelToRead.flatMap { nextChannelToRead -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation)? in + return ( + nextChannelToRead.peer, + nextChannelToRead.threadData.flatMap { threadData -> (id: Int64, data: MessageHistoryThreadData) in + return (threadData.id, threadData.data) + }, + nextChannelToRead.unreadCount, + nextChannelToRead.location + ) + } self.chatDisplayNode.historyNode.nextChannelToReadDisplayName = contentData.state.nextChannelToReadDisplayName self.updateNextChannelToReadVisibility() @@ -286,6 +296,18 @@ extension ChatControllerImpl { didDisplayActionsPanel = true } + var previousInvitationPeers: [EnginePeer] = [] + if let requestsState = previousState.requestsState { + previousInvitationPeers = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3)) + } + var previousInvitationRequestsPeersDismissed = false + if let dismissedInvitationRequests = previousState.dismissedInvitationRequests, Set(previousInvitationPeers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) { + previousInvitationRequestsPeersDismissed = true + } + if let requestsState = previousState.requestsState, requestsState.count > 0 && !previousInvitationRequestsPeersDismissed { + didDisplayActionsPanel = true + } + var displayActionsPanel = false if let contactStatus = contentData.state.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { if !peerStatusSettings.flags.isEmpty { @@ -307,6 +329,18 @@ extension ChatControllerImpl { displayActionsPanel = true } + var invitationPeers: [EnginePeer] = [] + if let requestsState = contentData.state.requestsState { + invitationPeers = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3)) + } + var invitationRequestsPeersDismissed = false + if let dismissedInvitationRequests = contentData.state.dismissedInvitationRequests, Set(invitationPeers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) { + invitationRequestsPeersDismissed = true + } + if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !invitationRequestsPeersDismissed { + displayActionsPanel = true + } + if displayActionsPanel != didDisplayActionsPanel { animated = true } @@ -411,7 +445,6 @@ extension ChatControllerImpl { if let dismissedInvitationRequests = contentData.state.dismissedInvitationRequests, Set(peers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) { peersDismissed = true } - if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !peersDismissed { if !context.contains(where: { switch $0 { diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index a8fcd18da8..15239ef82f 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -434,14 +434,6 @@ func updateChatPresentationInterfaceStateImpl( selfController.presentationInterfaceState = updatedChatPresentationInterfaceState - /*if selfController.chatDisplayNode.chatLocation != selfController.presentationInterfaceState.chatLocation { - let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatLocation.threadId, to: selfController.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in - return direction ? .right : .left - } - let tabSwitchDirection = selfController.currentChatSwitchDirection ?? defaultDirection - selfController.chatDisplayNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation, transition: transition, tabSwitchDirection: tabSwitchDirection) - }*/ - selfController.updateSlowmodeStatus() switch updatedChatPresentationInterfaceState.inputMode { @@ -498,20 +490,11 @@ func updateChatPresentationInterfaceStateImpl( } var buttonsAnimated = transition.isAnimated - if selfController.currentChatSwitchDirection != nil { - buttonsAnimated = false - } if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { if selfController.rightNavigationButton != button { if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action { buttonsAnimated = false } - if case .replyThread = selfController.chatLocation { - buttonsAnimated = false - } - if let channel = updatedChatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { - buttonsAnimated = false - } selfController.rightNavigationButton = button } } else if let _ = selfController.rightNavigationButton { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9ccc51a8d3..ad2de43f0f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9751,6 +9751,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let navigationSnapshot = self.chatTitleView?.prepareSnapshotState() + let avatarSnapshot = self.chatInfoNavigationButton?.buttonItem.customDisplayNode?.view.window != nil ? (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.prepareSnapshotState() : nil let chatLocationContextHolder = Atomic(value: nil) let historyNode = self.chatDisplayNode.createHistoryNodeForChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder) @@ -9780,108 +9781,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.chatTitleView?.animateFromSnapshot(navigationSnapshot, direction: mappedAnimationDirection) - /*if let rightBarButtonItems = self.navigationItem.rightBarButtonItems { - for i in 0 ..< rightBarButtonItems.count { - let item = rightBarButtonItems[i] - if let customDisplayNode = item.customDisplayNode { - customDisplayNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - //customDisplayNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - - let _ = rightBarButtonItemSnapshots - /*if i < rightBarButtonItemSnapshots.count { - let (snapshotItem, snapshotFrame) = rightBarButtonItemSnapshots[i] - if let targetSuperview = customDisplayNode.view.superview { - snapshotItem.frame = targetSuperview.convert(snapshotFrame, from: self.view) - targetSuperview.addSubview(snapshotItem) - - snapshotItem.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, completion: { [weak snapshotItem] _ in - snapshotItem?.removeFromSuperview() - }) - snapshotItem.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - } - }*/ - } - } - }*/ + } + + if let avatarSnapshot, self.chatInfoNavigationButton?.buttonItem.customDisplayNode?.view.window != nil { + (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshot) } self.currentChatSwitchDirection = nil - self.isUpdatingChatLocationThread = false }) - - /* - - // - - let rightBarButtonItemSnapshots: [(UIView, CGRect)] = (self.navigationItem.rightBarButtonItems ?? []).compactMap { item -> (UIView, CGRect)? in - guard let view = item.customDisplayNode?.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) else { - return nil - } - return (snapshotView, view.convert(view.bounds, to: self.view)) - } - - let isReady = Promise() - let chatLocationContextHolder = Atomic(value: nil) - self.reloadChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder, historyNode: historyNode, isReady: isReady) - - self.isUpdatingChatLocationThread = true - self.updateChatLocationThreadDisposable?.dispose() - self.updateChatLocationThreadDisposable = (isReady.get() - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak self] _ in - guard let self else { - return - } - self.isUpdatingChatLocationThread = false - - self.currentChatSwitchDirection = animationDirection - self.updateChatPresentationInterfaceState(animated: animationDirection != nil, interactive: false, { presentationInterfaceState in - return presentationInterfaceState.updatedChatLocation(updatedChatLocation) - }) - - if let navigationSnapshot, let animationDirection { - let mappedAnimationDirection: ChatTitleView.AnimateFromSnapshotDirection - switch animationDirection { - case .up: - mappedAnimationDirection = .up - case .down: - mappedAnimationDirection = .down - case .left: - mappedAnimationDirection = .left - case .right: - mappedAnimationDirection = .right - } - - self.chatTitleView?.animateFromSnapshot(navigationSnapshot, direction: mappedAnimationDirection) - if let rightBarButtonItems = self.navigationItem.rightBarButtonItems { - for i in 0 ..< rightBarButtonItems.count { - let item = rightBarButtonItems[i] - if let customDisplayNode = item.customDisplayNode { - customDisplayNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - //customDisplayNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - - let _ = rightBarButtonItemSnapshots - /*if i < rightBarButtonItemSnapshots.count { - let (snapshotItem, snapshotFrame) = rightBarButtonItemSnapshots[i] - if let targetSuperview = customDisplayNode.view.superview { - snapshotItem.frame = targetSuperview.convert(snapshotFrame, from: self.view) - targetSuperview.addSubview(snapshotItem) - - snapshotItem.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, completion: { [weak snapshotItem] _ in - snapshotItem?.removeFromSuperview() - }) - snapshotItem.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - } - }*/ - } - } - } - } - - self.currentChatSwitchDirection = nil - })*/ } public var contentContainerNode: ASDisplayNode { diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index c69a8d4719..275dafc0a9 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -57,6 +57,30 @@ extension ChatControllerImpl { case dismiss } + struct NextChannelToRead: Equatable { + struct ThreadData: Equatable { + let id: Int64 + let data: MessageHistoryThreadData + + init(id: Int64, data: MessageHistoryThreadData) { + self.id = id + self.data = data + } + } + + let peer: EnginePeer + let threadData: ThreadData? + let unreadCount: Int + let location: TelegramEngine.NextUnreadChannelLocation + + init(peer: EnginePeer, threadData: ThreadData?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) { + self.peer = peer + self.threadData = threadData + self.unreadCount = unreadCount + self.location = location + } + } + struct State { var peerView: PeerView? var threadInfo: EngineMessageHistoryThread.Info? @@ -72,7 +96,7 @@ extension ChatControllerImpl { var contactStatus: ChatContactStatus? var adMessage: Message? var offerNextChannelToRead: Bool = false - var nextChannelToRead: (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation)? + var nextChannelToRead: NextChannelToRead? var nextChannelToReadDisplayName: Bool = false var isNotAccessible: Bool = false var hasBots: Bool = false @@ -178,8 +202,8 @@ extension ChatControllerImpl { let chatThemeEmoticonPromise = Promise() let chatWallpaperPromise = Promise() - var inviteRequestsContext: PeerInvitationImportersContext? - private var inviteRequestsDisposable = MetaDisposable() + private(set) var inviteRequestsContext: PeerInvitationImportersContext? + private var inviteRequestsDisposable: Disposable? init( context: AccountContext, @@ -192,10 +216,14 @@ extension ChatControllerImpl { currentChatListFilter: Int32?, customChatNavigationStack: [EnginePeer.Id]?, presentationData: PresentationData, - historyNode: ChatHistoryListNodeImpl + historyNode: ChatHistoryListNodeImpl, + inviteRequestsContext: PeerInvitationImportersContext? ) { self.chatLocation = chatLocation self.presentationData = presentationData + + self.inviteRequestsContext = inviteRequestsContext + let strings = self.presentationData.strings let chatLocationPeerId: PeerId? = chatLocation.peerId @@ -972,11 +1000,23 @@ extension ChatControllerImpl { let previousState = strongSelf.state - strongSelf.state.offerNextChannelToRead = true - strongSelf.state.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in - return (peer: nextPeer, threadData: nil, unreadCount: 0, location: .same) + var isUpdated = false + + if !strongSelf.state.offerNextChannelToRead { + strongSelf.state.offerNextChannelToRead = true + isUpdated = true + } + let nextChannelToRead = nextPeer.flatMap { nextPeer -> NextChannelToRead in + return NextChannelToRead(peer: nextPeer, threadData: nil, unreadCount: 0, location: .same) + } + if strongSelf.state.nextChannelToRead != nextChannelToRead { + strongSelf.state.nextChannelToRead = nextChannelToRead + isUpdated = true + } + if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) { + strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + isUpdated = true } - strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 let nextPeerId = nextPeer?.id @@ -992,7 +1032,9 @@ extension ChatControllerImpl { } } - strongSelf.onUpdated?(previousState) + if isUpdated { + strongSelf.onUpdated?(previousState) + } }) } } else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil { @@ -1012,11 +1054,23 @@ extension ChatControllerImpl { let previousState = strongSelf.state - strongSelf.state.offerNextChannelToRead = true - strongSelf.state.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in - return (peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location) + var isUpdated = false + + if !strongSelf.state.offerNextChannelToRead { + strongSelf.state.offerNextChannelToRead = true + isUpdated = true + } + let nextChannelToRead = nextPeer.flatMap { nextPeer -> NextChannelToRead in + return NextChannelToRead(peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location) + } + if strongSelf.state.nextChannelToRead != nextChannelToRead { + strongSelf.state.nextChannelToRead = nextChannelToRead + isUpdated = true + } + if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) { + strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + isUpdated = true } - strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 let nextPeerId = nextPeer?.peer.id @@ -1032,7 +1086,9 @@ extension ChatControllerImpl { } } - strongSelf.onUpdated?(previousState) + if isUpdated { + strongSelf.onUpdated?(previousState) + } }) } } @@ -1570,13 +1626,27 @@ extension ChatControllerImpl { let previousState = strongSelf.state - strongSelf.state.offerNextChannelToRead = true - strongSelf.state.nextChannelToRead = nextThreadData.flatMap { nextThreadData -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in - return (peer: EnginePeer(channel), threadData: nextThreadData, unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same) - } - strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + var isUpdated = false - strongSelf.onUpdated?(previousState) + if !strongSelf.state.offerNextChannelToRead { + strongSelf.state.offerNextChannelToRead = true + isUpdated = true + } + let nextChannelToRead = nextThreadData.flatMap { nextThreadData -> NextChannelToRead in + return NextChannelToRead(peer: EnginePeer(channel), threadData: NextChannelToRead.ThreadData(id: nextThreadData.id, data: nextThreadData.data), unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same) + } + if strongSelf.state.nextChannelToRead != nextChannelToRead { + strongSelf.state.nextChannelToRead = nextChannelToRead + isUpdated = true + } + if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) { + strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3 + isUpdated = true + } + + if isUpdated { + strongSelf.onUpdated?(previousState) + } }) } } @@ -2129,19 +2199,6 @@ extension ChatControllerImpl { if strongSelf.inviteRequestsContext == nil { let inviteRequestsContext = context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil)) strongSelf.inviteRequestsContext = inviteRequestsContext - - strongSelf.inviteRequestsDisposable.set((combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId))).startStrict(next: { [weak strongSelf] requestsState, dismissedInvitationRequests in - guard let strongSelf else { - return - } - - let previousState = strongSelf.state - - strongSelf.state.requestsState = requestsState - strongSelf.state.dismissedInvitationRequests = dismissedInvitationRequests - - strongSelf.onUpdated?(previousState) - })) } else if let inviteRequestsContext = strongSelf.inviteRequestsContext { let _ = (inviteRequestsContext.state |> take(1) @@ -2151,6 +2208,30 @@ extension ChatControllerImpl { } }) } + + if chatLocation.threadId == nil { + if strongSelf.inviteRequestsDisposable == nil, let inviteRequestsContext = strongSelf.inviteRequestsContext { + strongSelf.inviteRequestsDisposable = combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId)).startStrict(next: { [weak strongSelf] requestsState, dismissedInvitationRequests in + guard let strongSelf else { + return + } + + let previousState = strongSelf.state + + strongSelf.state.requestsState = requestsState + strongSelf.state.dismissedInvitationRequests = dismissedInvitationRequests + + strongSelf.onUpdated?(previousState) + }) + } + } else { + strongSelf.state.requestsState = nil + strongSelf.state.dismissedInvitationRequests = [] + } + } else { + strongSelf.inviteRequestsContext = nil + strongSelf.state.requestsState = nil + strongSelf.state.dismissedInvitationRequests = [] } var isUpdated = false @@ -2196,7 +2277,7 @@ extension ChatControllerImpl { self.cachedDataDisposable?.dispose() self.premiumGiftSuggestionDisposable?.dispose() self.translationStateDisposable?.dispose() - self.inviteRequestsDisposable.dispose() + self.inviteRequestsDisposable?.dispose() } } } diff --git a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift index acf983bd3e..1fb6330ee5 100644 --- a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift @@ -108,12 +108,27 @@ private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode { } final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { + private final class Params { + let width: CGFloat + let leftInset: CGFloat + let rightInset: CGFloat + let interfaceState: ChatPresentationInterfaceState + + init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, interfaceState: ChatPresentationInterfaceState) { + self.width = width + self.leftInset = leftInset + self.rightInset = rightInset + self.interfaceState = interfaceState + } + } + private let context: AccountContext private let separatorNode: ASDisplayNode private let closeButton: HighlightableButtonNode - private var button: UIButton? + private let button: HighlightableButtonNode + private let buttonTitle: ImmediateTextNode private let avatarsContext: AnimatedAvatarSetContext private var avatarsContent: AnimatedAvatarSetContext.Content? @@ -127,6 +142,8 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { private var peers: [EnginePeer] = [] private var count: Int32 = 0 + private var params: Params? + init(context: AccountContext) { self.context = context @@ -137,6 +154,10 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.displaysAsynchronously = false + self.button = HighlightableButtonNode() + self.buttonTitle = ImmediateTextNode() + self.buttonTitle.anchorPoint = CGPoint() + self.avatarsContext = AnimatedAvatarSetContext() self.avatarsNode = AnimatedAvatarSetNode() @@ -150,6 +171,12 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) self.addSubnode(self.closeButton) + self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.addSubnode(self.button) + + self.buttonTitle.isUserInteractionEnabled = false + self.button.addSubnode(self.buttonTitle) + self.addSubnode(self.avatarsNode) self.addSubnode(self.activateAreaNode) @@ -161,12 +188,16 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { self.peers = peers self.count = count - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.avatarsContent = self.avatarsContext.update(peers: peers, animated: false) - self.button?.setTitle(presentationData.strings.Conversation_RequestsToJoin(count), for: []) + + if let params = self.params { + let _ = self.updateLayout(width: params.width, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate, interfaceState: params.interfaceState) + } } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult { + self.params = Params(width: width, leftInset: leftInset, rightInset: rightInset, interfaceState: interfaceState) + if interfaceState.theme !== self.theme { self.theme = interfaceState.theme @@ -181,21 +212,15 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize)) - if self.button == nil { - let view = UIButton() - view.titleLabel?.font = Font.regular(16.0) - view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor, for: []) - view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.7), for: [.highlighted]) - view.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside]) - self.view.addSubview(view) - self.button = view - } + self.buttonTitle.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_RequestsToJoin(self.count), font: Font.regular(16.0), textColor: interfaceState.theme.rootController.navigationBar.accentTextColor) - self.button?.setTitle(interfaceState.strings.Conversation_RequestsToJoin(self.count), for: []) + transition.updateFrame(node: self.button, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: panelHeight))) - let maxInset = max(contentRightInset, leftInset) - let buttonWidth = floor(width - maxInset * 2.0) - self.button?.frame = CGRect(origin: CGPoint(x: maxInset, y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight)) + let titleSize = self.buttonTitle.updateLayout(CGSize(width: width - leftInset - 90.0 - contentRightInset, height: 100.0)) + var buttonTitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - titleSize.width) * 0.5), y: floor((panelHeight - titleSize.height) * 0.5)), size: titleSize) + buttonTitleFrame.origin.x = max(buttonTitleFrame.minX, leftInset + 90.0) + transition.updatePosition(node: self.buttonTitle, position: buttonTitleFrame.origin) + self.buttonTitle.bounds = CGRect(origin: CGPoint(), size: buttonTitleFrame.size) let initialPanelHeight = panelHeight transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))