From 3fd1ef362c4f13c26848f5980eeda39dab58a62c Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 7 Oct 2025 21:55:39 +0800 Subject: [PATCH] Input updates --- .../ChatRecordingPreviewInputPanelNode.swift | 15 +- .../Sources/ChatFloatingTopicsPanel.swift | 298 ++++++++++++++++++ .../Sources/ChatSideTopicsPanel.swift | 113 +++---- .../Sources/ChatTextInputPanelNode.swift | 145 ++++----- .../Sources/GlassBackgroundComponent.swift | 78 +++-- .../TelegramUI/Sources/ChatController.swift | 3 - .../Sources/ChatControllerNode.swift | 132 ++++---- .../ChatInterfaceTitlePanelNodes.swift | 166 +++++----- 8 files changed, 643 insertions(+), 307 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift index a122276cfe..f8c3ce8074 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -70,7 +70,7 @@ final class ChatRecordingPreviewViewForOverlayContent: UIView, ChatInputPanelVie } final class PlayButtonNode: ASDisplayNode { - let backgroundView: GlassBackgroundView + let backgroundView: UIImageView let playButton: HighlightableButtonNode fileprivate let playPauseIconNode: PlayPauseIconNode let durationLabel: MediaPlayerTimeTextNode @@ -78,7 +78,9 @@ final class PlayButtonNode: ASDisplayNode { var pressed: () -> Void = {} init(theme: PresentationTheme) { - self.backgroundView = GlassBackgroundView(frame: CGRect()) + self.backgroundView = UIImageView() + self.backgroundView.isUserInteractionEnabled = true + self.backgroundView.clipsToBounds = true self.playButton = HighlightableButtonNode() self.playButton.displaysAsynchronously = false @@ -96,8 +98,8 @@ final class PlayButtonNode: ASDisplayNode { self.view.addSubview(self.backgroundView) self.addSubnode(self.playButton) - self.backgroundView.contentView.addSubview(self.playPauseIconNode.view) - self.backgroundView.contentView.addSubview(self.durationLabel.view) + self.backgroundView.addSubview(self.playPauseIconNode.view) + self.backgroundView.addSubview(self.durationLabel.view) self.playButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } @@ -118,7 +120,10 @@ final class PlayButtonNode: ASDisplayNode { let backgroundFrame = buttonSize.centered(in: CGRect(origin: .zero, size: size)) transition.updateFrame(view: self.backgroundView, frame: backgroundFrame) - self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.4)), transition: ComponentTransition(transition)) + if self.backgroundView.image?.size.height != backgroundFrame.height { + self.backgroundView.image = generateStretchableFilledCircleImage(diameter: backgroundFrame.height, color: .white)?.withRenderingMode(.alwaysTemplate) + } + self.backgroundView.tintColor = theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7) self.playPauseIconNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 1.0 - UIScreenPixel), size: CGSize(width: 21.0, height: 21.0)) diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift index 8b13789179..70d7a6d2da 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift @@ -1 +1,299 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramPresentationData +import TelegramCore +import GlassBackgroundComponent +public final class ChatFloatingTopicsPanel: Component { + public typealias EnvironmentType = ChatSidePanelEnvironment + + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let location: ChatSideTopicsPanel.Location + public let peerId: EnginePeer.Id + public let kind: ChatSideTopicsPanel.Kind + public let topicId: Int64? + public let controller: () -> ViewController? + public let togglePanel: () -> Void + public let updateTopicId: (Int64?, Bool) -> Void + public let openDeletePeer: (Int64) -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + location: ChatSideTopicsPanel.Location, + peerId: EnginePeer.Id, + kind: ChatSideTopicsPanel.Kind, + topicId: Int64?, + controller: @escaping () -> ViewController?, + togglePanel: @escaping () -> Void, + updateTopicId: @escaping (Int64?, Bool) -> Void, + openDeletePeer: @escaping (Int64) -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.location = location + self.peerId = peerId + self.kind = kind + self.topicId = topicId + self.controller = controller + self.togglePanel = togglePanel + self.updateTopicId = updateTopicId + self.openDeletePeer = openDeletePeer + } + + public static func ==(lhs: ChatFloatingTopicsPanel, rhs: ChatFloatingTopicsPanel) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.location != rhs.location { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.kind != rhs.kind { + return false + } + if lhs.topicId != rhs.topicId { + return false + } + return true + } + + public final class View: UIView { + private let containerView: GlassBackgroundContainerView + + private var sidePanelBackgroundView: GlassBackgroundView? + private var sidePanel: ComponentView? + private var topPanelBackgroundView: GlassBackgroundView? + private var topPanel: ComponentView? + + override public init(frame: CGRect) { + self.containerView = GlassBackgroundContainerView() + + super.init(frame: frame) + + self.addSubview(self.containerView) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = super.hitTest(point, with: event) else { + return nil + } + if result === self || result === self.containerView.contentView { + return nil + } + return result + } + + func update(component: ChatFloatingTopicsPanel, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[ChatSidePanelEnvironment.self].value + + if case .side = component.location { + let sidePanel: ComponentView + var sidePanelTransition = transition + if let current = self.sidePanel { + sidePanel = current + } else { + sidePanelTransition = sidePanelTransition.withAnimation(.none) + sidePanel = ComponentView() + self.sidePanel = sidePanel + } + let sidePanelBackgroundView: GlassBackgroundView + if let current = self.sidePanelBackgroundView { + sidePanelBackgroundView = current + } else { + sidePanelBackgroundView = GlassBackgroundView() + self.sidePanelBackgroundView = sidePanelBackgroundView + } + let sidePanelSize = sidePanel.update( + transition: sidePanelTransition, + component: AnyComponent(ChatSideTopicsPanel( + context: component.context, + theme: component.theme, + strings: component.strings, + location: .side, + peerId: component.peerId, + kind: component.kind, + topicId: component.topicId, + controller: component.controller, + togglePanel: component.togglePanel, + updateTopicId: component.updateTopicId, + openDeletePeer: component.openDeletePeer + )), + environment: { + ChatSidePanelEnvironment(insets: UIEdgeInsets( + top: 0.0, + left: 0.0, + bottom: environment.insets.bottom, + right: 0.0 + )) + }, + containerSize: CGSize(width: 72.0 + 8.0, height: availableSize.height) + ) + let sidePanelFrame = CGRect(origin: CGPoint(), size: CGSize(width: 8.0 + 80.0, height: availableSize.height - 8.0 - 8.0 - environment.insets.bottom)) + let sidePanelBackgroundFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: 80.0, height: availableSize.height - 8.0 - 8.0 - 8.0 - environment.insets.bottom)) + if let sidePanelView = sidePanel.view as? ChatSideTopicsPanel.View { + if sidePanelView.superview == nil { + sidePanelView.layer.cornerRadius = 20.0 + sidePanelView.clipsToBounds = true + self.addSubview(sidePanelView) + self.containerView.contentView.addSubview(sidePanelBackgroundView) + + sidePanelView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sidePanelSize.height, height: 8.0 + 40.0)) + + sidePanelBackgroundView.frame = CGRect(origin: sidePanelBackgroundFrame.origin, size: CGSize(width: sidePanelBackgroundFrame.width, height: 40.0)) + sidePanelBackgroundView.update(size: sidePanelBackgroundView.frame.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate) + } + transition.setFrame(view: sidePanelView, frame: sidePanelFrame, completion: { [weak sidePanelView] flag in + if flag { + sidePanelView?.clipsToBounds = false + } + }) + + transition.setFrame(view: sidePanelBackgroundView, frame: sidePanelBackgroundFrame) + sidePanelBackgroundView.update(size: sidePanelBackgroundFrame.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition) + } + } else if let sidePanel = self.sidePanel { + self.sidePanel = nil + if let sidePanelView = sidePanel.view as? ChatSideTopicsPanel.View { + let sidePanelBackgroundView = self.sidePanelBackgroundView + self.sidePanelBackgroundView = nil + if let sidePanelBackgroundView { + transition.setFrame(view: sidePanelBackgroundView, frame: CGRect(origin: sidePanelBackgroundView.frame.origin, size: CGSize(width: sidePanelBackgroundView.bounds.width, height: 40.0)), completion: { [weak sidePanelBackgroundView] _ in + sidePanelBackgroundView?.removeFromSuperview() + }) + sidePanelBackgroundView.update(size: sidePanelBackgroundView.bounds.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition) + } + + sidePanelView.clipsToBounds = true + transition.setFrame(view: sidePanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sidePanelView.bounds.width, height: 8.0 + 40.0)), completion: { [weak sidePanelView] _ in + sidePanelView?.removeFromSuperview() + }) + } + } + + if case .top = component.location { + let topPanel: ComponentView + var topPanelTransition = transition + if let current = self.topPanel { + topPanel = current + } else { + topPanelTransition = topPanelTransition.withAnimation(.none) + topPanel = ComponentView() + self.topPanel = topPanel + } + let topPanelBackgroundView: GlassBackgroundView + if let current = self.topPanelBackgroundView { + topPanelBackgroundView = current + } else { + topPanelBackgroundView = GlassBackgroundView() + self.topPanelBackgroundView = topPanelBackgroundView + } + let _ = topPanel.update( + transition: topPanelTransition, + component: AnyComponent(ChatSideTopicsPanel( + context: component.context, + theme: component.theme, + strings: component.strings, + location: .top, + peerId: component.peerId, + kind: component.kind, + topicId: component.topicId, + controller: component.controller, + togglePanel: component.togglePanel, + updateTopicId: component.updateTopicId, + openDeletePeer: component.openDeletePeer + )), + environment: { + ChatSidePanelEnvironment(insets: UIEdgeInsets( + top: 0.0, + left: 0.0, + bottom: 0.0, + right: 0.0 + )) + }, + containerSize: CGSize(width: availableSize.width, height: 8.0 + 40.0) + ) + let topPanelFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width - 8.0, height: 8.0 + 40.0)) + let topPanelBackgroundFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: availableSize.width - 8.0 - 8.0, height: 40.0)) + if let topPanelView = topPanel.view as? ChatSideTopicsPanel.View { + if topPanelView.superview == nil { + topPanelView.clipsToBounds = true + topPanelView.layer.cornerRadius = 20.0 + self.addSubview(topPanelView) + self.containerView.contentView.addSubview(topPanelBackgroundView) + topPanelView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 80.0 + 8.0, height: topPanelFrame.height)) + + topPanelBackgroundView.frame = CGRect(origin: topPanelBackgroundFrame.origin, size: CGSize(width: 80.0, height: topPanelBackgroundFrame.height)) + topPanelBackgroundView.update(size: topPanelBackgroundView.bounds.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate) + } + transition.setFrame(view: topPanelView, frame: topPanelFrame, completion: { [weak topPanelView] flag in + if flag { + topPanelView?.clipsToBounds = false + } + }) + + transition.setFrame(view: topPanelBackgroundView, frame: topPanelBackgroundFrame) + topPanelBackgroundView.update(size: topPanelBackgroundFrame.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition) + } + } else if let topPanel = self.topPanel { + self.topPanel = nil + if let topPanelView = topPanel.view as? ChatSideTopicsPanel.View { + if let topPanelBackgroundView = self.topPanelBackgroundView { + self.topPanelBackgroundView = nil + + transition.setFrame(view: topPanelBackgroundView, frame: CGRect(origin: topPanelBackgroundView.frame.origin, size: CGSize(width: 80.0, height: topPanelBackgroundView.bounds.height)), completion: { [weak topPanelBackgroundView] _ in + topPanelBackgroundView?.removeFromSuperview() + }) + topPanelBackgroundView.update(size: topPanelBackgroundView.bounds.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition) + } + topPanelView.clipsToBounds = true + transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 8.0 + 72.0, height: topPanelView.bounds.height)), completion: { [weak topPanelView] _ in + topPanelView?.removeFromSuperview() + }) + } + } + + transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: availableSize)) + self.containerView.update(size: availableSize, transition: transition) + + return availableSize + } + + public func topicIndex(threadId: Int64?) -> Int? { + if let sidePanelView = self.sidePanel?.view as? ChatSideTopicsPanel.View { + return sidePanelView.topicIndex(threadId: threadId) + } else if let topPanelView = self.topPanel?.view as? ChatSideTopicsPanel.View { + return topPanelView.topicIndex(threadId: threadId) + } else { + return nil + } + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index 0d249563a1..d703291312 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -1743,54 +1743,32 @@ public final class ChatSideTopicsPanel: Component { ], locations: [0.0, 1.0], direction: .vertical) { image.draw(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: image.size.width)), blendMode: .destinationOut, alpha: 1.0) } - - /*let innerSize = size - let shadowInset: CGFloat = 32.0 - - let addInnerShadow: (CGPoint, CGFloat, UIColor) -> Void = { position, blur, shadowColor in - if let image = generateImage(CGSize(width: size.width + shadowInset * 2.0, height: size.height + shadowInset * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - let spreadRect = CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: innerSize).insetBy(dx: -0.25, dy: -0.25) - let spreadPath = UIBezierPath( - roundedRect: spreadRect, - cornerRadius: min(spreadRect.width, spreadRect.height) * 0.5 - ).cgPath - - context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor) - context.setFillColor(shadowColor.cgColor) - let enclosingRect = spreadRect.insetBy(dx: -10000.0, dy: -10000.0) - for _ in 0 ..< 3 { - context.addPath(UIBezierPath(rect: enclosingRect).cgPath) - context.addPath(spreadPath) - context.fillPath(using: .evenOdd) - } - - /*let cleanRect = CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: innerSize) - let cleanPath = UIBezierPath( - roundedRect: cleanRect, - cornerRadius: min(cleanRect.width, cleanRect.height) * 0.5 - ).cgPath - context.setBlendMode(.copy) - context.setFillColor(UIColor.clear.cgColor) - context.addPath(UIBezierPath(rect: enclosingRect).cgPath) - context.addPath(cleanPath) - context.fillPath(using: .evenOdd) - context.setBlendMode(.normal)*/ - }) { - image.draw(in: CGRect(origin: CGPoint(x: -shadowInset, y: -shadowInset), size: CGSize(width: image.size.width, height: image.size.height)), blendMode: .normal, alpha: 1.0) - } - } - - addInnerShadow(CGPoint(x: 0.0, y: -6.0), 10.0, .red) - addInnerShadow(CGPoint(x: 0.0, y: 6.0), 10.0, .red)*/ - - })?.stretchableImage(withLeftCapWidth: Int(cornerRadius) + 1, topCapHeight: Int(cornerRadius) + 1) case .top: - self.scrollViewMask.image = generateGradientImage(size: CGSize(width: 8.0, height: 8.0), colors: [ - UIColor(white: 1.0, alpha: 0.0), - UIColor(white: 1.0, alpha: 1.0) - ], locations: [0.0, 1.0], direction: .horizontal)?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 0) + let cornerRadius: CGFloat = 20.0 + self.scrollViewMask.image = generateImage(CGSize(width: 8.0 + 1.0 + cornerRadius * 2.0, height: 1.0 + cornerRadius * 2.0), rotatedContext: { size, context in + UIGraphicsPushContext(context) + defer { + UIGraphicsPopContext() + } + + context.clear(CGRect(origin: CGPoint(), size: size)) + + let spreadPath = UIBezierPath( + roundedRect: CGRect(origin: CGPoint(x: -cornerRadius, y: 0.0), size: CGSize(width: size.width + cornerRadius, height: size.height)), + cornerRadius: cornerRadius + ).cgPath + context.setFillColor(UIColor.black.cgColor) + context.addPath(spreadPath) + context.fillPath() + + if let image = generateGradientImage(size: CGSize(width: 8.0, height: 8.0), colors: [ + UIColor(white: 1.0, alpha: 1.0), + UIColor(white: 1.0, alpha: 0.0) + ], locations: [0.0, 1.0], direction: .horizontal) { + image.draw(in: CGRect(origin: CGPoint(), size: CGSize(width: image.size.width, height: size.height)), blendMode: .destinationOut, alpha: 1.0) + } + })?.stretchableImage(withLeftCapWidth: Int(cornerRadius) + 1, topCapHeight: Int(cornerRadius) + 1) } } let themeUpdated = self.component?.theme !== component.theme @@ -1893,11 +1871,11 @@ public final class ChatSideTopicsPanel: Component { let itemFrame: CGRect switch component.location { case .side: - itemFrame = CGRect(origin: CGPoint(x: 8.0 + 4.0, y: directionContainerInset + 8.0), size: itemSize) + itemFrame = CGRect(origin: CGPoint(x: 8.0 + 4.0, y: directionContainerInset + 6.0), size: itemSize) directionContainerInset += itemSize.height case .top: - itemFrame = CGRect(origin: CGPoint(x: directionContainerInset, y: 0.0), size: itemSize) - directionContainerInset += itemSize.width - 14.0 + itemFrame = CGRect(origin: CGPoint(x: 12.0, y: 6.0), size: itemSize) + directionContainerInset += itemSize.width - 8.0 } itemTransition.setPosition(layer: itemView.layer, position: itemFrame.center) @@ -1920,13 +1898,13 @@ public final class ChatSideTopicsPanel: Component { scrollFrame = CGRect(origin: CGPoint(x: additionalInsets.left, y: directionContainerInset + environment.insets.top + additionalInsets.top), size: scrollSize) listContentInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) case .top: - scrollSize = CGSize(width: availableSize.width - directionContainerInset, height: availableSize.height) - scrollFrame = CGRect(origin: CGPoint(x: directionContainerInset, y: 0.0), size: scrollSize) + additionalInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0.0, right: 8.0) + scrollSize = CGSize(width: availableSize.width - additionalInsets.left - additionalInsets.right - directionContainerInset, height: availableSize.height - additionalInsets.top) + scrollFrame = CGRect(origin: CGPoint(x: additionalInsets.left + directionContainerInset, y: additionalInsets.top), size: scrollSize) listContentInsets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0) - additionalInsets = UIEdgeInsets() } - if case .side = component.location { + /*if case .side = component.location { let background: ComponentView if let current = self.background { background = current @@ -1953,7 +1931,34 @@ public final class ChatSideTopicsPanel: Component { } transition.setFrame(view: backgroundView, frame: backgroundFrame) } - } + } else { + let background: ComponentView + if let current = self.background { + background = current + } else { + background = ComponentView() + self.background = background + } + let backgroundFrame = CGRect(origin: CGPoint(x: additionalInsets.left, y: environment.insets.top + additionalInsets.top), size: CGSize(width: scrollFrame.width + directionContainerInset, height: scrollFrame.height)) + let _ = background.update( + transition: transition, + component: AnyComponent(GlassBackgroundComponent( + size: backgroundFrame.size, + cornerRadius: 20.0, + isDark: component.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)) + )), + environment: {}, + containerSize: backgroundFrame.size + ) + + if let backgroundView = background.view { + if backgroundView.superview == nil { + self.insertSubview(backgroundView, at: 0) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + }*/ self.scrollContainerView.frame = scrollFrame diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 4c9d60dcac..04773cc12d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -217,14 +217,14 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre } public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate { - public let clippingNode: ASDisplayNode public let textPlaceholderNode: ImmediateTextNodeWithEntities + private let glassBackgroundContainer: GlassBackgroundContainerView + public var textLockIconNode: ASImageNode? public var contextPlaceholderNode: TextNode? public var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode? - public let textInputContainerBackgroundView: GlassBackgroundView - public let textInputContainer: ASDisplayNode + private let textInputContainerBackgroundView: GlassBackgroundView private let accessoryPanelContainer: UIView public let textInputNodeClippingContainer: ASDisplayNode public let textInputSeparator: GlassBackgroundView.ContentColorView @@ -512,19 +512,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } self.inputMenu = TextInputMenu(hasSpoilers: hasSpoilers, hasQuotes: hasQuotes) - self.clippingNode = ASDisplayNode() - self.clippingNode.clipsToBounds = false + self.glassBackgroundContainer = GlassBackgroundContainerView() self.textInputContainerBackgroundView = GlassBackgroundView(frame: CGRect()) self.textInputContainerBackgroundView.isUserInteractionEnabled = false - self.textInputContainer = ASDisplayNode() - self.textInputContainer.view.addSubview(self.textInputContainerBackgroundView) - self.textInputContainer.clipsToBounds = false - self.accessoryPanelContainer = UIView() self.accessoryPanelContainer.clipsToBounds = true - self.textInputContainer.view.addSubview(self.accessoryPanelContainer) self.textInputNodeClippingContainer = ASDisplayNode() self.textInputNodeClippingContainer.clipsToBounds = true @@ -580,7 +574,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.attachmentButton.isAccessibilityElement = true self.attachmentButtonBackground = GlassBackgroundView(frame: CGRect()) - self.attachmentButton.addSubview(self.attachmentButtonBackground) + self.attachmentButtonBackground.contentView.addSubview(self.attachmentButton) self.attachmentButtonIcon = GlassBackgroundView.ContentImageView() self.attachmentButtonIcon.isUserInteractionEnabled = false @@ -610,6 +604,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg super.init() + self.view.addSubview(self.glassBackgroundContainer) + self.slowModeButton.requestUpdate = { [weak self] in self?.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) } @@ -645,8 +641,6 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.enableBounceAnimations = false }*/ - self.addSubnode(self.clippingNode) - self.sendAsAvatarContainerNode.activated = { [weak self] gesture, _ in guard let strongSelf = self else { return @@ -809,10 +803,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.searchLayoutClearButton.alpha = 0.0 self.searchLayoutClearButtonIcon.alpha = 0.0 - self.clippingNode.addSubnode(self.textInputContainer) - self.clippingNode.addSubnode(self.textInputBackgroundNode) + self.glassBackgroundContainer.contentView.addSubview(self.textInputBackgroundNode.view) + self.glassBackgroundContainer.contentView.addSubview(self.textInputContainerBackgroundView) + self.textInputContainerBackgroundView.contentView.addSubview(self.accessoryPanelContainer) self.textInputContainerBackgroundView.contentView.addSubview(self.textPlaceholderNode.view) + self.textInputContainerBackgroundView.contentView.addSubview(self.textInputNodeClippingContainer.view) self.menuButton.view.addSubview(self.menuButtonBackgroundView) self.menuButton.addSubnode(self.menuButtonClippingNode) @@ -822,19 +818,19 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.sendAsAvatarContainerNode.addSubnode(self.sendAsAvatarReferenceNode) self.sendAsAvatarReferenceNode.addSubnode(self.sendAsAvatarNode) self.sendAsAvatarButtonNode.addSubnode(self.sendAsAvatarContainerNode) - self.clippingNode.addSubnode(self.sendAsAvatarButtonNode) + self.glassBackgroundContainer.contentView.addSubview(self.sendAsAvatarButtonNode.view) - self.clippingNode.addSubnode(self.menuButton) - self.clippingNode.view.addSubview(self.attachmentButton) - self.clippingNode.addSubnode(self.attachmentButtonDisabledNode) + self.glassBackgroundContainer.contentView.addSubview(self.menuButton.view) + self.glassBackgroundContainer.contentView.addSubview(self.attachmentButtonBackground) + self.glassBackgroundContainer.contentView.addSubview(self.attachmentButtonDisabledNode.view) - self.clippingNode.addSubnode(self.startButton) + self.glassBackgroundContainer.contentView.addSubview(self.startButton.view) - self.clippingNode.addSubnode(self.sendActionButtons) - self.clippingNode.addSubnode(self.mediaActionButtons) - self.clippingNode.addSubnode(self.counterTextNode) + self.glassBackgroundContainer.contentView.addSubview(self.sendActionButtons.view) + self.glassBackgroundContainer.contentView.addSubview(self.mediaActionButtons.view) + self.glassBackgroundContainer.contentView.addSubview(self.counterTextNode.view) - self.clippingNode.addSubnode(self.slowModeButton) + self.glassBackgroundContainer.contentView.addSubview(self.slowModeButton.view) self.textInputContainerBackgroundView.contentView.addSubview(self.searchLayoutClearButton) self.textInputContainerBackgroundView.contentView.addSubview(self.searchLayoutClearButtonIcon) @@ -889,7 +885,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.viewOnceButton.addTarget(self, action: #selector(self.viewOncePressed), forControlEvents: [.touchUpInside]) self.recordMoreButton.addTarget(self, action: #selector(self.recordMorePressed), forControlEvents: [.touchUpInside]) - self.addSubnode(self.recordMoreButton) + self.view.addSubview(self.recordMoreButton.view) } required init?(coder aDecoder: NSCoder) { @@ -946,7 +942,6 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg textInputNode.keyboardAppearance = keyboardAppearance textInputNode.tintColor = tintColor textInputNode.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: -13.0) - self.textInputContainer.addSubnode(self.textInputNodeClippingContainer) self.textInputNodeClippingContainer.addSubnode(textInputNode) textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true textInputNode.isUserInteractionEnabled = !self.sendingTextDisabled @@ -1064,17 +1059,16 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string } - private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { - let textFieldInsets = self.textFieldInsets(metrics: metrics) + private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics, bottomInset: CGFloat) -> CGFloat { + let textFieldInsets = self.textFieldInsets(metrics: metrics, bottomInset: bottomInset) return max(33.0, maxHeight - (textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) } - private func calculateTextFieldMetrics(width: CGFloat, sendActionControlsWidth: CGFloat, maxHeight: CGFloat, metrics: LayoutMetrics) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat, isOverflow: Bool) { + private func calculateTextFieldMetrics(width: CGFloat, sendActionControlsWidth: CGFloat, maxHeight: CGFloat, metrics: LayoutMetrics, bottomInset: CGFloat) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat, isOverflow: Bool) { let maxHeight = max(maxHeight, 40.0) - let textFieldInsets = self.textFieldInsets(metrics: metrics) - - let fieldMaxHeight = textFieldMaxHeight(maxHeight, metrics: metrics) + let textFieldInsets = self.textFieldInsets(metrics: metrics, bottomInset: bottomInset) + let fieldMaxHeight = self.textFieldMaxHeight(maxHeight, metrics: metrics, bottomInset: bottomInset) var accessoryButtonsWidth: CGFloat = 0.0 var firstButton = true @@ -1126,13 +1120,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg return (accessoryButtonsWidth, textFieldHeight, isOverflow) } - private func textFieldInsets(metrics: LayoutMetrics) -> UIEdgeInsets { + private func textFieldInsets(metrics: LayoutMetrics, bottomInset: CGFloat) -> UIEdgeInsets { let insets = UIEdgeInsets(top: 0.0, left: 54.0, bottom: 0.0, right: 8.0) return insets } - private func panelHeight(textFieldHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { - let textFieldInsets = self.textFieldInsets(metrics: metrics) + private func panelHeight(textFieldHeight: CGFloat, metrics: LayoutMetrics, bottomInset: CGFloat) -> CGFloat { + let textFieldInsets = self.textFieldInsets(metrics: metrics, bottomInset: bottomInset) let result = textFieldHeight + textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom return result } @@ -1171,14 +1165,14 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let menuContentDelta = (self.startButton.frame.width - self.menuButton.frame.width) / 2.0 menuIconSnapshotView.frame = self.menuButtonIconNode.frame.offsetBy(dx: self.menuButton.frame.minX, dy: self.menuButton.frame.minY) - self.view.addSubview(menuIconSnapshotView) + self.glassBackgroundContainer.contentView.addSubview(menuIconSnapshotView) menuIconSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak menuIconSnapshotView] _ in menuIconSnapshotView?.removeFromSuperview() }) transition.updatePosition(layer: menuIconSnapshotView.layer, position: CGPoint(x: menuIconSnapshotView.center.x + menuContentDelta, y: self.startButton.position.y)) menuTextSnapshotView.frame = self.menuButtonTextNode.frame.offsetBy(dx: self.menuButton.frame.minX + 19.0, dy: self.menuButton.frame.minY) - self.view.addSubview(menuTextSnapshotView) + self.glassBackgroundContainer.contentView.addSubview(menuTextSnapshotView) menuTextSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak menuTextSnapshotView, weak self] _ in menuTextSnapshotView?.removeFromSuperview() self?.animatingTransition = false @@ -1220,14 +1214,14 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg var menuIconSnapshotViewFrame = self.menuButtonIconNode.frame.offsetBy(dx: self.menuButton.frame.minX + menuContentDelta, dy: self.menuButton.frame.minY) menuIconSnapshotViewFrame.origin.y = self.startButton.position.y - menuIconSnapshotViewFrame.height / 2.0 menuIconSnapshotView.frame = menuIconSnapshotViewFrame - self.view.addSubview(menuIconSnapshotView) + self.glassBackgroundContainer.contentView.addSubview(menuIconSnapshotView) menuIconSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) transition.updatePosition(layer: menuIconSnapshotView.layer, position: CGPoint(x: menuIconSnapshotView.center.x - menuContentDelta, y: self.menuButton.position.y)) var menuTextSnapshotViewFrame = self.menuButtonTextNode.frame.offsetBy(dx: self.menuButton.frame.minX + 19.0 + menuContentDelta, dy: self.menuButton.frame.minY) menuTextSnapshotViewFrame.origin.y = self.startButton.position.y - menuTextSnapshotViewFrame.height / 2.0 menuTextSnapshotView.frame = menuTextSnapshotViewFrame - self.view.addSubview(menuTextSnapshotView) + self.glassBackgroundContainer.contentView.addSubview(menuTextSnapshotView) menuTextSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) transition.updatePosition(layer: menuTextSnapshotView.layer, position: CGPoint(x: menuTextSnapshotView.center.x - menuContentDelta, y: self.menuButton.position.y), completion: { [weak self, weak menuIconSnapshotView, weak menuTextSnapshotView] _ in self?.animatingTransition = false @@ -1286,6 +1280,14 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let previousAdditionalSideInsets = self.validLayout?.4 self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) + var leftInset = leftInset + var rightInset = rightInset + + if bottomInset <= 32.0 { + leftInset += 18.0 + rightInset += 18.0 + } + let placeholderColor: UIColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor var transition = transition @@ -1388,7 +1390,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg attachmentButtonAlpha = 0.0 } - transition.updateAlpha(layer: self.attachmentButton.layer, alpha: attachmentButtonAlpha) + transition.updateAlpha(layer: self.attachmentButtonBackground.layer, alpha: attachmentButtonAlpha) self.attachmentButton.isEnabled = isMediaEnabled && !isRecording self.attachmentButton.accessibilityTraits = (!isSlowmodeActive || isMediaEnabled) ? [.button] : [.button, .notEnabled] self.attachmentButtonDisabledNode.isHidden = !isSlowmodeActive || isMediaEnabled @@ -1918,8 +1920,6 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition = .animated(duration: 0.3, curve: .easeInOut) } - var leftInset = leftInset - let textInputBackgroundWidthOffset: CGFloat = 0.0 var attachmentButtonX: CGFloat = hideOffset.x + leftInset + leftMenuInset + 8.0 if !displayMediaButton || mediaRecordingState != nil { @@ -1949,8 +1949,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } let baseWidth = width - leftInset - leftMenuInset - rightInset - rightSlowModeInset - let (accessoryButtonsWidth, textFieldHeight, isTextFieldOverflow) = self.calculateTextFieldMetrics(width: baseWidth, sendActionControlsWidth: sendActionButtonsSize.width, maxHeight: maxHeight, metrics: metrics) - var panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) + let (accessoryButtonsWidth, textFieldHeight, isTextFieldOverflow) = self.calculateTextFieldMetrics(width: baseWidth, sendActionControlsWidth: sendActionButtonsSize.width, maxHeight: maxHeight, metrics: metrics, bottomInset: bottomInset) + var panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics, bottomInset: bottomInset) if displayBotStartButton { panelHeight += 27.0 } @@ -2008,7 +2008,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.menuButton.isUserInteractionEnabled = hasMenuButton self.sendAsAvatarButtonNode.isUserInteractionEnabled = hasMenuButton && isSendAsButton - var textFieldInsets = self.textFieldInsets(metrics: metrics) + var textFieldInsets = self.textFieldInsets(metrics: metrics, bottomInset: bottomInset) if additionalSideInsets.right > 0.0 { textFieldInsets.right += additionalSideInsets.right / 3.0 } @@ -2034,7 +2034,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } else { audioRecordingInfoContainerNode = ASDisplayNode() self.audioRecordingInfoContainerNode = audioRecordingInfoContainerNode - self.clippingNode.addSubnode(audioRecordingInfoContainerNode) + self.glassBackgroundContainer.contentView.addSubview(audioRecordingInfoContainerNode.view) } var animateTimeSlideIn = false @@ -2161,7 +2161,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if animateTimeSlideIn { var previousAudioRecordingTimeFrame = audioRecordingTimeFrame - previousAudioRecordingTimeFrame.origin.x = self.textInputContainer.frame.minX + 34.0 + previousAudioRecordingTimeFrame.origin.x = self.textInputContainerBackgroundView.frame.minX + 34.0 audioRecordingTimeNode.frame = previousAudioRecordingTimeFrame audioRecordingTimeNode.layer.animateAlpha(from: 0, to: 1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) @@ -2183,9 +2183,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg audioRecordingDotView.image = generateStretchableFilledCircleImage(diameter: 10.0, color: UIColor(rgb: 0xFF2D55)) self.audioRecordingDotView = audioRecordingDotView - self.clippingNode.view.insertSubview(audioRecordingDotView, belowSubview: self.menuButton.view) + self.glassBackgroundContainer.contentView.insertSubview(audioRecordingDotView, belowSubview: self.menuButton.view) - let previousDotFrame = CGRect(origin: CGPoint(x: self.textInputContainer.frame.minX + 16.0, y: dotFrame.minY), size: dotFrame.size) + let previousDotFrame = CGRect(origin: CGPoint(x: self.textInputContainerBackgroundView.frame.minX + 16.0, y: dotFrame.minY), size: dotFrame.size) audioRecordingDotView.center = previousDotFrame.center transition.updatePosition(layer: audioRecordingDotView.layer, position: dotFrame.center) @@ -2367,7 +2367,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg mediaPreviewPanelNode.alpha = 0.0 mediaPreviewPanelNode.frame = mediaPreviewPanelFrame - self.textInputContainer.addSubnode(mediaPreviewPanelNode) + self.textInputContainerBackgroundView.contentView.addSubview(mediaPreviewPanelNode.view) mediaPreviewPanelNode.tintMaskView.alpha = 0.0 mediaPreviewPanelNode.tintMaskView.frame = mediaPreviewPanelFrame self.textInputContainerBackgroundView.maskContentView.addSubview(mediaPreviewPanelNode.tintMaskView) @@ -2395,19 +2395,18 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg contentHeight += textInputHeight contentHeight += textFieldInsets.bottom - let previousTextInputContainerBackgroundFrame = self.textInputContainer.frame + let previousTextInputContainerBackgroundFrame = self.textInputContainerBackgroundView.frame let textInputContainerBackgroundFrame = CGRect(x: hideOffset.x + leftInset + textFieldInsets.left, y: hideOffset.y + textFieldInsets.top, width: textInputWidth, height: contentHeight) let textInputFrame = textInputContainerBackgroundFrame - transition.updateFrame(node: self.textInputContainer, frame: textInputContainerBackgroundFrame) transition.updateFrame(view: self.accessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size)) - transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size)) + transition.updateFrame(view: self.textInputContainerBackgroundView, frame: textInputContainerBackgroundFrame) var textInputContainerBackgroundTransition = ComponentTransition(transition) if useBounceAnimation, case let .animated(_, curve) = transition, case .spring = curve { textInputContainerBackgroundTransition = textInputContainerBackgroundTransition.withUserData(GlassBackgroundView.TransitionFlagBounce()) } - self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: textInputContainerBackgroundTransition) + self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: false, transition: textInputContainerBackgroundTransition) transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputContainerBackgroundFrame) transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha) @@ -2640,7 +2639,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } var actionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - actionButtonsSize.height), size: actionButtonsSize) - if inputHasText || self.extendedSearchLayout { + if inputHasText || self.extendedSearchLayout || hasMediaDraft { actionButtonsFrame.origin.x = width + 8.0 } transition.updateFrame(node: self.mediaActionButtons, frame: actionButtonsFrame) @@ -2691,7 +2690,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } return true } - self.clippingNode.insertSubnode(mediaRecordingAccessibilityArea, aboveSubnode: self.mediaActionButtons) + self.glassBackgroundContainer.contentView.insertSubview(mediaRecordingAccessibilityArea.view, aboveSubview: self.mediaActionButtons.view) } self.mediaActionButtons.isAccessibilityElement = false let size: CGFloat = 120.0 @@ -2723,11 +2722,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0)) - self.attachmentButtonBackground.frame = CGRect(origin: CGPoint(), size: attachmentButtonFrame.size) self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition)) - transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame) - transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame) + transition.updateFrame(layer: self.attachmentButtonBackground.layer, frame: attachmentButtonFrame) + transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(), size: attachmentButtonFrame.size)) + transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButtonBackground.frame) if let image = self.attachmentButtonIcon.image { transition.updateFrame(view: self.attachmentButtonIcon, frame: CGRect(origin: CGPoint(x: floor((attachmentButtonFrame.width - image.size.width) * 0.5), y: floor((attachmentButtonFrame.height - image.size.height) * 0.5)), size: image.size)) @@ -2741,7 +2740,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg attachmentImageNode = TransformImageNode() attachmentImageNode.isUserInteractionEnabled = false self.attachmentImageNode = attachmentImageNode - self.addSubnode(attachmentImageNode) + self.glassBackgroundContainer.contentView.addSubview(attachmentImageNode.view) } let attachmentImageSize = CGSize(width: 26.0, height: 26.0) @@ -2816,12 +2815,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg recordMoreIsVisible = true } - var clippingDelta: CGFloat = 0.0 + /*var clippingDelta: CGFloat = 0.0 if case let .media(_, _, focused) = interfaceState.inputMode, focused { clippingDelta = -panelHeight } transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight))) - transition.updateSublayerTransformOffset(layer: self.clippingNode.layer, offset: CGPoint(x: 0.0, y: clippingDelta)) + transition.updateSublayerTransformOffset(layer: self.clippingNode.layer, offset: CGPoint(x: 0.0, y: clippingDelta))*/ let viewOnceSize = self.viewOnceButton.update(theme: interfaceState.theme) let viewOnceButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 50.0 - UIScreenPixel, y: -152.0), size: viewOnceSize) @@ -2890,7 +2889,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let previousContextPanelFrame = CGRect(origin: CGPoint(x: previousTextInputContainerBackgroundFrame.minX, y: contentHeight - maxOverlayHeight), size: CGSize(width: previousTextInputContainerBackgroundFrame.width, height: max(0.0, maxOverlayHeight - contentHeight + contextPanelBottomInset))) if contextPanel.container.superview == nil { - self.view.insertSubview(contextPanel.container, belowSubview: self.clippingNode.view) + self.view.insertSubview(contextPanel.container, at: 0) contextPanel.container.addSubview(contextPanel.panel.view) contextPanel.container.mask = contextPanel.mask let maskSize = floor(minimalInputHeight) @@ -2931,6 +2930,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg ) } + let containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight + 64.0)) + transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame) + self.glassBackgroundContainer.update(size: containerFrame.size, transition: ComponentTransition(transition)) + return contentHeight } @@ -3454,14 +3457,14 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.counterTextNode.attributedText = NSAttributedString(string: "", font: counterFont, textColor: .black) } - if let (width, leftInset, rightInset, _, _, maxHeight, _, metrics, _, _) = self.validLayout { + if let (width, leftInset, rightInset, bottomInset, _, maxHeight, _, metrics, _, _) = self.validLayout { var composeButtonsOffset: CGFloat = 0.0 if self.extendedSearchLayout { composeButtonsOffset = 40.0 } - let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - self.leftMenuInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset, sendActionControlsWidth: self.sendActionButtons.bounds.width, maxHeight: maxHeight, metrics: metrics) - let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) + let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - self.leftMenuInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset, sendActionControlsWidth: self.sendActionButtons.bounds.width, maxHeight: maxHeight, metrics: metrics, bottomInset: bottomInset) + let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics, bottomInset: bottomInset) var textFieldMinHeight: CGFloat = 33.0 if let presentationInterfaceState = self.presentationInterfaceState { textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) @@ -3918,9 +3921,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } private func updateTextHeight(animated: Bool) { - if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, _, metrics, _, _) = self.validLayout { - let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset, sendActionControlsWidth: self.sendActionButtons.bounds.width, maxHeight: maxHeight, metrics: metrics) - let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, _, metrics, _, _) = self.validLayout { + let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset, sendActionControlsWidth: self.sendActionButtons.bounds.width, maxHeight: maxHeight, metrics: metrics, bottomInset: bottomInset) + let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics, bottomInset: bottomInset) if !self.bounds.size.height.isEqual(to: panelHeight) { self.updateHeight(animated) } else { @@ -4765,8 +4768,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } public func frameForAttachmentButton() -> CGRect? { - if !self.attachmentButton.alpha.isZero { - return self.attachmentButton.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0) + if !self.attachmentButtonBackground.alpha.isZero { + return self.attachmentButtonBackground.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0) } return nil } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index 41c0090065..5b424de352 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -282,7 +282,6 @@ public class GlassBackgroundView: UIView { private let backgroundNode: NavigationBackgroundNode? private let nativeView: UIVisualEffectView? - private let nativeContainerView: UIVisualEffectView? private let nativeParamsView: EffectSettingsContainerView? private let foregroundView: UIImageView? @@ -313,25 +312,18 @@ public class GlassBackgroundView: UIView { let glassEffect = UIGlassEffect(style: .regular) glassEffect.isInteractive = false let nativeView = UIVisualEffectView(effect: glassEffect) - //nativeView.layer.anchorPoint = CGPoint() self.nativeView = nativeView - let glassContainerEffect = UIGlassContainerEffect() - let nativeContainerView = UIVisualEffectView(effect: glassContainerEffect) - self.nativeContainerView = nativeContainerView - nativeContainerView.contentView.addSubview(nativeView) - let nativeParamsView = EffectSettingsContainerView(frame: CGRect()) self.nativeParamsView = nativeParamsView - nativeParamsView.addSubview(nativeContainerView) + nativeParamsView.addSubview(nativeView) self.foregroundView = nil self.shadowView = nil } else { self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 8.0) self.nativeView = nil - self.nativeContainerView = nil self.nativeParamsView = nil self.foregroundView = UIImageView() @@ -372,36 +364,30 @@ public class GlassBackgroundView: UIView { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - /*if let nativeContainerView = self.nativeContainerView { - if let result = nativeContainerView.hitTest(self.convert(point, to: nativeContainerView), with: event) { + if let nativeView = self.nativeView { + if let result = nativeView.hitTest(self.convert(point, to: nativeView), with: event) { return result } - }*/ + } return nil } public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, transition: ComponentTransition) { - if let nativeContainerView = self.nativeContainerView, let nativeView = self.nativeView, nativeView.bounds.size != size { + if let nativeView = self.nativeView, nativeView.bounds.size != size { if transition.animation.isImmediate { nativeView.layer.cornerRadius = cornerRadius nativeView.frame = CGRect(origin: CGPoint(), size: size) - nativeContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0))) } else { nativeView.layer.cornerRadius = cornerRadius let nativeFrame = CGRect(origin: CGPoint(), size: size) - let nativeContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0))) if transition.userData(TransitionFlagBounce.self) != nil { transition.containedViewLayoutTransition.updatePositionSpring(layer: nativeView.layer, position: nativeFrame.center) transition.containedViewLayoutTransition.updateBoundsSpring(layer: nativeView.layer, bounds: CGRect(origin: CGPoint(), size: nativeFrame.size)) - - transition.containedViewLayoutTransition.updatePositionSpring(layer: nativeContainerView.layer, position: nativeContainerFrame.center) - transition.containedViewLayoutTransition.updateBoundsSpring(layer: nativeContainerView.layer, bounds: CGRect(origin: CGPoint(), size: nativeContainerFrame.size)) } else { transition.setFrame(view: nativeView, frame: nativeFrame) - transition.setFrame(view: nativeContainerView, frame: nativeContainerFrame) } } } @@ -459,7 +445,7 @@ public class GlassBackgroundView: UIView { if let foregroundView = self.foregroundView { foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: tintColor.color) } else { - if let nativeParamsView = self.nativeParamsView, let nativeContainerView = self.nativeContainerView, let nativeView { + if let nativeParamsView = self.nativeParamsView, let nativeView = self.nativeView { if #available(iOS 26.0, *) { let glassEffect = UIGlassEffect(style: .regular) switch tintColor.kind { @@ -480,7 +466,7 @@ public class GlassBackgroundView: UIView { nativeParamsView.lumaMax = 1.0 } - nativeContainerView.overrideUserInterfaceStyle = isDark ? .dark : .light + nativeView.overrideUserInterfaceStyle = isDark ? .dark : .light } } } @@ -500,23 +486,65 @@ public class GlassBackgroundView: UIView { public final class GlassBackgroundContainerView: UIView { private final class ContentView: UIView { - } - private let contentViewImpl: ContentView + private let legacyView: ContentView? + private let nativeView: UIVisualEffectView? + public var contentView: UIView { - return self.contentViewImpl + if let nativeView = self.nativeView { + return nativeView.contentView + } else { + return self.legacyView! + } } public override init(frame: CGRect) { - self.contentViewImpl = ContentView() + if #available(iOS 26.0, *) { + let effect = UIGlassContainerEffect() + effect.spacing = 7.0 + self.nativeView = UIVisualEffectView(effect: effect) + self.legacyView = nil + } else { + self.nativeView = nil + self.legacyView = ContentView() + } super.init(frame: frame) + + if let nativeView = self.nativeView { + self.addSubview(nativeView) + } else if let legacyView = self.legacyView { + self.addSubview(legacyView) + } } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override public func didAddSubview(_ subview: UIView) { + super.didAddSubview(subview) + + if subview !== self.nativeView && subview !== self.legacyView { + assertionFailure() + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = self.contentView.hitTest(point, with: event) else { + return nil + } + return result + } + + public func update(size: CGSize, transition: ComponentTransition) { + if let nativeView = self.nativeView { + transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size)) + } else if let legacyView = self.legacyView { + transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size)) + } + } } public final class VariableBlurView: UIVisualEffectView { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 3f1da2d2bf..1032806be7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5114,9 +5114,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.navigationBar?.contentNode != nil { return false } - if strongSelf.chatDisplayNode.leftPanel != nil { - return false - } return true } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index b0bb4848a8..512d85cc38 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -235,8 +235,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private var chatTranslationPanel: ChatTranslationPanelNode? - private var leftPanelContainer: ChatControllerTitlePanelNodeContainer - private(set) var leftPanel: (component: AnyComponentWithIdentity, view: ComponentView)? + private var floatingTopicsPanelContainer: ChatControllerTitlePanelNodeContainer + private var floatingTopicsPanel: (view: ComponentView, component: ChatFloatingTopicsPanel)? private var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode? @@ -374,7 +374,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let contentBounds = self.loadingNode.frame loadingPlaceholderNode.frame = contentBounds if let loadingPlaceholderNode = self.loadingPlaceholderNode, let validLayout = self.validLayout { - loadingPlaceholderNode.updateLayout(size: contentBounds.size, isSidebarOpen: self.leftPanel != nil, insets: self.loadingNodeInsets, metrics: validLayout.0.metrics, transition: .immediate) + var isSidebarOpen = false + if let floatingTopicsPanel = self.floatingTopicsPanel { + isSidebarOpen = floatingTopicsPanel.component.location == .side + } + loadingPlaceholderNode.updateLayout(size: contentBounds.size, isSidebarOpen: isSidebarOpen, insets: self.loadingNodeInsets, metrics: validLayout.0.metrics, transition: .immediate) loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: .immediate) } } @@ -457,7 +461,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.titleAccessoryPanelContainer = ChatControllerTitlePanelNodeContainer() self.titleAccessoryPanelContainer.clipsToBounds = true - self.leftPanelContainer = ChatControllerTitlePanelNodeContainer() + self.floatingTopicsPanelContainer = ChatControllerTitlePanelNodeContainer() setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat) @@ -819,7 +823,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.wrappingNode.contentNode.addSubnode(self.contentContainerNode) self.contentContainerNode.contentNode.addSubnode(self.backgroundNode) self.contentContainerNode.contentNode.addSubnode(self.historyNodeContainer) - self.contentContainerNode.contentNode.addSubnode(self.leftPanelContainer) + self.contentContainerNode.contentNode.addSubnode(self.floatingTopicsPanelContainer) if let navigationBar = self.navigationBar { self.contentContainerNode.contentNode.addSubnode(navigationBar) @@ -1338,25 +1342,32 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.titleTopicsAccessoryPanelNode = nil } - let defaultLeftPanelWidth: CGFloat = 72.0 + 8.0 - let leftPanelLeftInset = defaultLeftPanelWidth - (72.0 + 8.0) + var floatingTopicsPanelInsets = UIEdgeInsets() + var dismissedFloatingTopicsPanel: (view: ComponentView, component: ChatFloatingTopicsPanel)? + var immediatelyLayoutFloatingTopicsNodeAndAnimateAppearance = false + var didChangeFloatingTopicsPanel = false - var leftPanelSize: CGSize? - var dismissedLeftPanel: (component: AnyComponentWithIdentity, view: ComponentView)? - var immediatelyLayoutLeftPanelNodeAndAnimateAppearance = false - if let leftPanelComponent = sidePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.leftPanel?.component, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { - if self.leftPanel?.component.id != leftPanelComponent.id { - dismissedLeftPanel = self.leftPanel - self.leftPanel = (leftPanelComponent, ComponentView()) - immediatelyLayoutLeftPanelNodeAndAnimateAppearance = true - } else if let leftPanel = self.leftPanel { - self.leftPanel = (leftPanelComponent, leftPanel.view) + if let floatingTopicsPanelComponent = floatingTopicsPanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { + if self.floatingTopicsPanel == nil { + self.floatingTopicsPanel = (ComponentView(), floatingTopicsPanelComponent) + immediatelyLayoutFloatingTopicsNodeAndAnimateAppearance = true + didChangeFloatingTopicsPanel = true + } else { + if self.floatingTopicsPanel?.component.location != floatingTopicsPanelComponent.location { + didChangeFloatingTopicsPanel = true + } + self.floatingTopicsPanel?.component = floatingTopicsPanelComponent } - leftPanelSize = CGSize(width: defaultLeftPanelWidth + 8.0, height: layout.size.height) - } else if let leftPanel = self.leftPanel { - dismissedLeftPanel = leftPanel - self.leftPanel = nil + switch floatingTopicsPanelComponent.location { + case .side: + floatingTopicsPanelInsets.left = 72.0 + 8.0 + 8.0 + case .top: + floatingTopicsPanelInsets.top = 40.0 + 8.0 + } + } else if let floatingTopicsPanel = self.floatingTopicsPanel { + self.floatingTopicsPanel = nil + dismissedFloatingTopicsPanel = floatingTopicsPanel } var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode? @@ -1559,7 +1570,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.feePanelNode = nil } - self.controllerInteraction.isSidePanelOpen = self.leftPanel != nil + var isSidebarOpen = false + if let floatingTopicsPanel = self.floatingTopicsPanel { + isSidebarOpen = floatingTopicsPanel.component.location == .side + } + self.controllerInteraction.isSidePanelOpen = isSidebarOpen var inputPanelNodeBaseHeight: CGFloat = 0.0 if let inputPanelNode = self.inputPanelNode { @@ -1626,7 +1641,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if inputTextPanelNode.isFocused { self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) } - let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) } if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) { inputPanelNodeHandlesTransition = true @@ -1638,7 +1653,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } else { dismissedInputPanelNode = self.inputPanelNode } - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.inputPanelNode = inputPanelNode if inputPanelNode.supernode !== self { @@ -1649,7 +1664,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) } } else { - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { @@ -1660,7 +1675,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing { if secondaryInputPanelNode !== self.secondaryInputPanelNode { dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode - let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.secondaryInputPanelNode = secondaryInputPanelNode if secondaryInputPanelNode.supernode == nil { @@ -1671,7 +1686,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) } } else { - let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { @@ -1835,7 +1850,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 200.0))) - self.titleAccessoryPanelContainer.hitTestExcludeInsets = UIEdgeInsets(top: 0.0, left: leftPanelSize?.width ?? 0.0, bottom: 0.0, right: 0.0) + self.titleAccessoryPanelContainer.hitTestExcludeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) transition.updateFrame(node: self.inputContextOverTextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) @@ -2061,7 +2076,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0)) } - if immediatelyLayoutLeftPanelNodeAndAnimateAppearance || dismissedLeftPanel != nil || immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance || dismissedTitleTopicsAccessoryPanelNode != nil { + if didChangeFloatingTopicsPanel || dismissedFloatingTopicsPanel != nil || immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance || dismissedTitleTopicsAccessoryPanelNode != nil { if transition.isAnimated { self.historyNode.resetScrolledToItem() self.historyNode.enableUnreadAlignment = false @@ -2260,14 +2275,12 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var loadingNodeInsets = visibleAreaInset loadingNodeInsets.left = layout.safeInsets.left loadingNodeInsets.right = layout.safeInsets.right - if let leftPanelSize { - loadingNodeInsets.left += leftPanelSize.width - } + loadingNodeInsets.left += floatingTopicsPanelInsets.left self.loadingNodeInsets = loadingNodeInsets self.loadingNode.updateLayout(size: contentBounds.size, insets: loadingNodeInsets, transition: transition) if let loadingPlaceholderNode = self.loadingPlaceholderNode { - loadingPlaceholderNode.updateLayout(size: contentBounds.size, isSidebarOpen: leftPanelSize != nil, insets: loadingNodeInsets, metrics: layout.metrics, transition: transition) + loadingPlaceholderNode.updateLayout(size: contentBounds.size, isSidebarOpen: floatingTopicsPanelInsets.left != 0.0, insets: loadingNodeInsets, metrics: layout.metrics, transition: transition) loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) } @@ -2314,9 +2327,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { listInsets.top += 8.0 } - if let leftPanelSize { - listInsets.left += leftPanelSize.width - } + listInsets.left += floatingTopicsPanelInsets.left + listInsets.bottom += floatingTopicsPanelInsets.top var emptyNodeInsets = insets emptyNodeInsets.bottom += inputPanelsHeight @@ -2408,7 +2420,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var customListAnimationTransition: ControlledTransition? if case let .animated(duration, curve) = transition { - if immediatelyLayoutLeftPanelNodeAndAnimateAppearance || dismissedLeftPanel != nil { + if didChangeFloatingTopicsPanel || dismissedFloatingTopicsPanel != nil { customListAnimationTransition = ControlledTransition(duration: duration, curve: curve, interactive: false) } } @@ -2472,36 +2484,37 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateBounds(node: self.inputPanelOverlayNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: layout.size), beginWithCurrentState: true) transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true) - let leftPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height)) - transition.updateFrame(node: self.leftPanelContainer, frame: leftPanelContainerFrame) - if let leftPanel = self.leftPanel, let leftPanelSize { - let leftPanelSize = leftPanel.view.update( - transition: immediatelyLayoutLeftPanelNodeAndAnimateAppearance ? .immediate :ComponentTransition(transition), - component: leftPanel.component.component, + let floatingTopicsPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height)) + transition.updateFrame(node: self.floatingTopicsPanelContainer, frame: floatingTopicsPanelContainerFrame) + if let floatingTopicsPanel = self.floatingTopicsPanel { + let floatingTopicsPanelSize = floatingTopicsPanel.view.update( + transition: immediatelyLayoutFloatingTopicsNodeAndAnimateAppearance ? .immediate : ComponentTransition(transition), + component: AnyComponent(floatingTopicsPanel.component), environment: { ChatSidePanelEnvironment(insets: UIEdgeInsets( top: 0.0, - left: leftPanelLeftInset, + left: layout.safeInsets.left, bottom: containerInsets.bottom + inputPanelsHeight + 8.0, - right: 0.0 + right: layout.safeInsets.right )) }, - containerSize: CGSize(width: defaultLeftPanelWidth, height: leftPanelSize.height - sidePanelTopInset) + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height - sidePanelTopInset) ) - let leftPanelFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: leftPanelSize) - if let leftPanelView = leftPanel.view.view { - if leftPanelView.superview == nil { - self.leftPanelContainer.view.addSubview(leftPanelView) + let floatingTopicsPanelFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: floatingTopicsPanelSize) + if let floatingTopicsPanelView = floatingTopicsPanel.view.view { + if floatingTopicsPanelView.superview == nil { + self.floatingTopicsPanelContainer.view.addSubview(floatingTopicsPanelView) } - if immediatelyLayoutLeftPanelNodeAndAnimateAppearance { - leftPanelView.frame = leftPanelFrame.offsetBy(dx: -leftPanelSize.width - 16.0, dy: 0.0) + if immediatelyLayoutFloatingTopicsNodeAndAnimateAppearance { + floatingTopicsPanelView.frame = floatingTopicsPanelFrame + } else { + transition.updateFrame(view: floatingTopicsPanelView, frame: floatingTopicsPanelFrame) } - transition.updateFrame(view: leftPanelView, frame: leftPanelFrame) } } - if let dismissedLeftPanel, let dismissedLeftPanelView = dismissedLeftPanel.view.view { - let dismissedLeftPanelSize = dismissedLeftPanel.view.update( + if let dismissedFloatingTopicsPanel, let dismissedFloatingTopicsPanelView = dismissedFloatingTopicsPanel.view.view { + /*let dismissedLeftPanelSize = dismissedLeftPanel.view.update( transition: ComponentTransition(transition), component: dismissedLeftPanel.component.component, environment: { @@ -2516,7 +2529,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { ) transition.updateFrame(view: dismissedLeftPanelView, frame: CGRect(origin: CGPoint(x: -layout.safeInsets.left - dismissedLeftPanelSize.width - 16.0, y: sidePanelTopInset), size: dismissedLeftPanelSize), completion: { [weak dismissedLeftPanelView] _ in dismissedLeftPanelView?.removeFromSuperview() - }) + })*/ + dismissedFloatingTopicsPanelView.removeFromSuperview() } if let navigationBarBackgroundContent = self.navigationBarBackgroundContent { @@ -5131,9 +5145,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode { leftIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: fromLocation) rightIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: toLocation) - } else if let leftPanelView = self.leftPanel?.view.view as? ChatSideTopicsPanel.View { - leftIndex = leftPanelView.topicIndex(threadId: fromLocation) - rightIndex = leftPanelView.topicIndex(threadId: toLocation) + } else if let floatingTopicsPanelView = self.floatingTopicsPanel?.view.view as? ChatFloatingTopicsPanel.View { + leftIndex = floatingTopicsPanelView.topicIndex(threadId: fromLocation) + rightIndex = floatingTopicsPanelView.topicIndex(threadId: toLocation) } guard let leftIndex, let rightIndex else { return nil diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 74117131a2..3852cc6de6 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -230,7 +230,8 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? { - if !(chatPresentationInterfaceState.subject?.isService ?? false) { + return nil + /*if !(chatPresentationInterfaceState.subject?.isService ?? false) { if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), chatPresentationInterfaceState.search == nil { let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation if !topicListDisplayModeOnTheSide, let peerId = chatPresentationInterfaceState.chatLocation.peerId { @@ -267,10 +268,10 @@ func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfa } } - return nil + return nil*/ } -func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AnyComponentWithIdentity?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> AnyComponentWithIdentity? { +func floatingTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatFloatingTopicsPanel? { guard let peerId = chatPresentationInterfaceState.chatLocation.peerId else { return nil } @@ -281,97 +282,82 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), chatPresentationInterfaceState.search == nil { let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation - if topicListDisplayModeOnTheSide { - return AnyComponentWithIdentity( - id: "topics", - component: AnyComponent(ChatSideTopicsPanel( - context: context, - theme: chatPresentationInterfaceState.theme, - strings: chatPresentationInterfaceState.strings, - location: .side, - peerId: peerId, - kind: .monoforum, - topicId: chatPresentationInterfaceState.chatLocation.threadId, - controller: { [weak interfaceInteraction] in - return interfaceInteraction?.chatController() - }, - togglePanel: { [weak interfaceInteraction] in - interfaceInteraction?.toggleChatSidebarMode() - }, - updateTopicId: { [weak interfaceInteraction] topicId, direction in - interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) - }, - openDeletePeer: { [weak interfaceInteraction] threadId in - guard let controller = interfaceInteraction?.chatController() as? ChatControllerImpl else { - return - } - controller.openDeleteMonoforumPeer(peerId: EnginePeer.Id(threadId)) - } - )) - ) - } + return ChatFloatingTopicsPanel( + context: context, + theme: chatPresentationInterfaceState.theme, + strings: chatPresentationInterfaceState.strings, + location: topicListDisplayModeOnTheSide ? .side : .top, + peerId: peerId, + kind: .monoforum, + topicId: chatPresentationInterfaceState.chatLocation.threadId, + controller: { [weak interfaceInteraction] in + return interfaceInteraction?.chatController() + }, + togglePanel: { [weak interfaceInteraction] in + interfaceInteraction?.toggleChatSidebarMode() + }, + updateTopicId: { [weak interfaceInteraction] topicId, direction in + interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) + }, + openDeletePeer: { [weak interfaceInteraction] threadId in + guard let controller = interfaceInteraction?.chatController() as? ChatControllerImpl else { + return + } + controller.openDeleteMonoforumPeer(peerId: EnginePeer.Id(threadId)) + } + ) } else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, chatPresentationInterfaceState.search == nil { let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation - if topicListDisplayModeOnTheSide { - return AnyComponentWithIdentity( - id: "topics", - component: AnyComponent(ChatSideTopicsPanel( - context: context, - theme: chatPresentationInterfaceState.theme, - strings: chatPresentationInterfaceState.strings, - location: .side, - peerId: peerId, - kind: .forum, - topicId: chatPresentationInterfaceState.chatLocation.threadId, - controller: { [weak interfaceInteraction] in - return interfaceInteraction?.chatController() - }, - togglePanel: { [weak interfaceInteraction] in - interfaceInteraction?.toggleChatSidebarMode() - }, - updateTopicId: { [weak interfaceInteraction] topicId, direction in - interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) - }, - openDeletePeer: { [weak interfaceInteraction] threadId in - guard let controller = interfaceInteraction?.chatController() as? ChatControllerImpl else { - return - } - controller.openDeleteMonoforumPeer(peerId: EnginePeer.Id(threadId)) - } - )) - ) - } + return ChatFloatingTopicsPanel( + context: context, + theme: chatPresentationInterfaceState.theme, + strings: chatPresentationInterfaceState.strings, + location: topicListDisplayModeOnTheSide ? .side : .top, + peerId: peerId, + kind: .forum, + topicId: chatPresentationInterfaceState.chatLocation.threadId, + controller: { [weak interfaceInteraction] in + return interfaceInteraction?.chatController() + }, + togglePanel: { [weak interfaceInteraction] in + interfaceInteraction?.toggleChatSidebarMode() + }, + updateTopicId: { [weak interfaceInteraction] topicId, direction in + interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) + }, + openDeletePeer: { [weak interfaceInteraction] threadId in + guard let controller = interfaceInteraction?.chatController() as? ChatControllerImpl else { + return + } + controller.openDeleteMonoforumPeer(peerId: EnginePeer.Id(threadId)) + } + ) } else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.isForum, chatPresentationInterfaceState.search == nil { let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation - if topicListDisplayModeOnTheSide { - return AnyComponentWithIdentity( - id: "topics", - component: AnyComponent(ChatSideTopicsPanel( - context: context, - theme: chatPresentationInterfaceState.theme, - strings: chatPresentationInterfaceState.strings, - location: .side, - peerId: peerId, - kind: .botForum, - topicId: chatPresentationInterfaceState.chatLocation.threadId, - controller: { [weak interfaceInteraction] in - return interfaceInteraction?.chatController() - }, - togglePanel: { [weak interfaceInteraction] in - interfaceInteraction?.toggleChatSidebarMode() - }, - updateTopicId: { [weak interfaceInteraction] topicId, direction in - interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) - }, - openDeletePeer: { [weak interfaceInteraction] threadId in - guard let controller = interfaceInteraction?.chatController() as? ChatControllerImpl else { - return - } - controller.openDeleteMonoforumPeer(peerId: EnginePeer.Id(threadId)) - } - )) - ) - } + return ChatFloatingTopicsPanel( + context: context, + theme: chatPresentationInterfaceState.theme, + strings: chatPresentationInterfaceState.strings, + location: topicListDisplayModeOnTheSide ? .side : .top, + peerId: peerId, + kind: .botForum, + topicId: chatPresentationInterfaceState.chatLocation.threadId, + controller: { [weak interfaceInteraction] in + return interfaceInteraction?.chatController() + }, + togglePanel: { [weak interfaceInteraction] in + interfaceInteraction?.toggleChatSidebarMode() + }, + updateTopicId: { [weak interfaceInteraction] topicId, direction in + interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) + }, + openDeletePeer: { [weak interfaceInteraction] threadId in + guard let controller = interfaceInteraction?.chatController() as? ChatControllerImpl else { + return + } + controller.openDeleteMonoforumPeer(peerId: EnginePeer.Id(threadId)) + } + ) } return nil