This commit is contained in:
Isaac
2025-09-17 11:18:26 +04:00
parent 0678d0ded0
commit 04765e0c36
67 changed files with 1405 additions and 1890 deletions

View File

@@ -13,6 +13,9 @@ import ChatPresentationInterfaceState
import ChatControllerInteraction
import ChatContextQuery
import ChatInputContextPanelNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
private struct CommandMenuChatInputContextPanelEntryStableId: Hashable {
let command: PeerCommand
@@ -61,6 +64,7 @@ private func preparedTransition(from fromEntries: [CommandMenuChatInputContextPa
}
final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundView: GlassBackgroundView
private let listView: ListView
private var currentEntries: [CommandMenuChatInputContextPanelEntry]?
@@ -70,11 +74,13 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
private let disposable = MetaDisposable()
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId, chatPresentationContext: ChatPresentationContext) {
self.backgroundView = GlassBackgroundView()
self.backgroundView.layer.anchorPoint = CGPoint()
self.listView = ListView()
self.listView.clipsToBounds = false
self.listView.isOpaque = false
self.listView.stackFromBottom = true
self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor
self.listView.limitHitTestToNodes = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.accessibilityPageScrolledString = { row, count in
@@ -86,8 +92,28 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
self.isOpaque = false
self.clipsToBounds = true
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
var topOffset: CGFloat = 0.0
switch offset {
case let .known(offset):
topOffset = max(0.0, -offset + self.listView.insets.top)
case .unknown:
break
case .none:
break
}
self.backgroundView.isHidden = false
self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset)
}
self.disposable.set((context.engine.peers.peerCommands(id: peerId)
|> deliverOnMainQueue).startStrict(next: { [weak self] results in
if let strongSelf = self {
@@ -178,9 +204,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
}
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: validLayout.0)
insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3)
insets.left = validLayout.1
insets.right = validLayout.2
insets.bottom = validLayout.3
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil))
@@ -193,21 +220,20 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
let position = strongSelf.listView.layer.position
strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset))
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView {
strongSelf.listView.position = position
}
if let topItemOffset {
let offset = strongSelf.listView.bounds.size.height - topItemOffset
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset))
transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset))
}
}
})
}
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat {
let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 4.7)
return max(size.height - minimumItemHeights, 0.0)
return max(size.height - bottomInset - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
@@ -215,9 +241,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
self.validLayout = (size, leftInset, rightInset, bottomInset)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size)
insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset)
insets.left = leftInset
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))
@@ -234,7 +261,6 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
self.prepareTransition(from: self.currentEntries, to: new)
@@ -249,11 +275,14 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
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 + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, 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)
} else {
completion()
}