From 8e93744c73545a04189cb7f9057a2091b0d94847 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 3 Oct 2025 21:21:19 +0800 Subject: [PATCH] Input field improvements --- .../Sources/ChatMessageBubbleItemNode.swift | 6 ++-- .../Sources/ChatTextInputPanelNode.swift | 30 ++++++++++++------- .../CommandChatInputContextPanelNode.swift | 15 ++++++---- .../HashtagChatInputContextPanelNode.swift | 5 ++-- .../MentionChatInputContextPanelNode.swift | 9 +++--- ...ntextResultsChatInputPanelButtonItem.swift | 10 ++++--- ...ListContextResultsChatInputPanelItem.swift | 4 +-- 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 1f0ac586f8..d61ead6c83 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -6883,11 +6883,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } if item.message.adAttribute != nil { - let transition: ContainedViewLayoutTransition = isScroll ? .animated(duration: 0.4, curve: .spring) : .immediate + let transition: ContainedViewLayoutTransition = isScroll ? .animated(duration: 0.25, curve: .easeInOut) : .immediate if case let .visible(_, rect) = self.visibility, rect.height >= 1.0 { - transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: 0.0)) + transition.updateAlpha(layer: self.layer, alpha: 1.0) } else { - transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: 200.0)) + transition.updateAlpha(layer: self.layer, alpha: 0.0) } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index aae94d8bfd..606447e208 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -1857,13 +1857,15 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg updatedButtons.append(itemAndButton!) } for (_, button) in self.accessoryItemButtons { - if animatedTransition { - if removeAccessoryButtons == nil { - removeAccessoryButtons = [] + if !updatedButtons.contains(where: { $0.1 === button }) { + if animatedTransition { + if removeAccessoryButtons == nil { + removeAccessoryButtons = [] + } + removeAccessoryButtons!.append(button) + } else { + button.removeFromSuperview() } - removeAccessoryButtons!.append(button) - } else { - button.removeFromSuperview() } } self.accessoryItemButtons = updatedButtons @@ -2602,9 +2604,14 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg for button in removeAccessoryButtons { let buttonFrame = CGRect(origin: CGPoint(x: button.frame.origin.x + additionalOffset, y: textInputFrame.maxY - minimalInputHeight), size: button.frame.size) transition.updateFrame(layer: button.layer, frame: buttonFrame) + let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) button.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) - button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak button] _ in - button?.removeFromSuperview() + alphaTransition.updateAlpha(layer: button.layer, alpha: 0.0) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25 * UIView.animationDurationFactor(), execute: { [weak button] in + if let button { + button.removeFromSuperview() + button.tintMask.removeFromSuperview() + } }) } } @@ -3799,7 +3806,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if self.sendActionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero { alphaTransition.updateAlpha(node: self.sendActionButtons.sendContainerNode, alpha: 1.0) blurTransitionIn.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: sendButtonBlurOut, toRadius: 0.0) - transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -14.0, y: 10.0)) + transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -18.0, y: 14.0)) if let sendButtonRadialStatusNode = self.sendActionButtons.sendButtonRadialStatusNode { alphaTransition.updateAlpha(node: sendButtonRadialStatusNode, alpha: 1.0) } @@ -3863,7 +3870,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if mediaInputIsActive && !hideExpandMediaInput { if self.mediaActionButtons.expandMediaInputButton.alpha.isZero { - alphaTransition.updateAlpha(layer: self.mediaActionButtons.expandMediaInputButton.layer, alpha: 1.0) + self.mediaActionButtons.expandMediaInputButton.alpha = 1.0 + if alphaTransition.isAnimated { + self.mediaActionButtons.expandMediaInputButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } } } else { if !self.mediaActionButtons.expandMediaInputButton.alpha.isZero { diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 1d184dea2a..457fd8a889 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -300,6 +300,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { self.backgroundView.layer.anchorPoint = CGPoint() self.listView = ListView() + self.listView.anchorPoint = CGPoint() self.listView.isOpaque = false self.listView.stackFromBottom = true self.listView.limitHitTestToNodes = true @@ -515,10 +516,15 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { insets.right = rightInset insets.bottom = bottomInset - transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) + transition.updateBounds(node: self.listView, bounds: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) + + var customListAnimationTransition: ControlledTransition? + if case let .animated(duration, curve) = transition { + customListAnimationTransition = ControlledTransition(duration: duration, curve: curve, interactive: false) + } let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve, customAnimationTransition: customListAnimationTransition) self.contentOffsetChangeTransition = ComponentTransition(transition) @@ -553,11 +559,10 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { if let topItemOffset { let offset = (self.listView.bounds.size.height - topItemOffset) - let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in completion() }) - self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.backgroundView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } else { completion() } diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index cff6806590..216bf4277b 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -370,11 +370,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { if let topItemOffset = topItemOffset { let offset = (self.listView.bounds.size.height - topItemOffset) - let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in completion() }) - self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.backgroundView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } else { completion() } diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index 9f09c047d5..880f062658 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -272,7 +272,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { } } - self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0)) + transition.updateBounds(layer: self.backgroundView.layer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0))) self.backgroundView.update( size: self.backgroundView.bounds.size, cornerRadius: 20.0, @@ -290,7 +290,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve, customAnimationTransition: nil) self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) @@ -312,11 +312,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { if let topItemOffset { let offset = (self.listView.bounds.size.height - topItemOffset) - let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in completion() }) - self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.backgroundView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } else { completion() } diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift index 806397d5dc..f79a3a6d7b 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift @@ -146,7 +146,7 @@ final class VerticalListContextResultsChatInputPanelButtonItemNode: ListViewItem let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: VerticalListContextResultsChatInputPanelButtonItemNode.itemHeight(style: item.style)), insets: UIEdgeInsets()) - return (nodeLayout, { _ in + return (nodeLayout, { animation in if let strongSelf = self { strongSelf.item = item @@ -164,11 +164,13 @@ final class VerticalListContextResultsChatInputPanelButtonItemNode: ListViewItem let _ = titleApply() - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.size.width) / 2.0), y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0) + titleOffsetY), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.size.width) / 2.0), y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0) + titleOffsetY), size: titleLayout.size) + animation.animator.updatePosition(layer: strongSelf.titleNode.layer, position: titleFrame.center, completion: nil) + strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width, height: UIScreenPixel)) + animation.animator.updateFrame(layer: strongSelf.separatorNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width, height: UIScreenPixel)), completion: nil) - strongSelf.buttonNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize) + animation.animator.updateFrame(layer: strongSelf.buttonNode.layer, frame: CGRect(origin: CGPoint(), size: nodeLayout.contentSize), completion: nil) } }) } diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift index 09f9d3bf08..274d87dbb1 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift @@ -275,7 +275,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: VerticalListContextResultsChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets()) - return (nodeLayout, { _ in + return (nodeLayout, { animation in if let strongSelf = self { strongSelf.item = item @@ -333,7 +333,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { strongSelf.separatorNode.isHidden = !mergedBottom - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) + animation.animator.updateFrame(layer: strongSelf.separatorNode.layer, frame: CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)), completion: nil) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))