diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift index 8babf32cbc..6803b945ca 100644 --- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift @@ -97,6 +97,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var titleAccessoryPanelNode: ChatTitleAccessoryPanelNode? private var inputPanelNode: ChatInputPanelNode? + private var secondaryInputPanelNode: ChatInputPanelNode? private var accessoryPanelNode: AccessoryPanelNode? private var inputContextPanelNode: ChatInputContextPanelNode? private let inputContextPanelContainer: ChatControllerTitlePanelNodeContainer @@ -133,6 +134,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } + private let updatingMessageMediaPromise = Promise<[MessageId: ChatUpdatingMessageMedia]>([:]) + var updatingMessageMedia: [MessageId: ChatUpdatingMessageMedia] = [:] { + didSet { + if self.updatingMessageMedia != oldValue { + self.updatingMessageMediaPromise.set(.single(self.updatingMessageMedia)) + } + } + } + var requestUpdateChatInterfaceState: (Bool, Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _, _ in } var requestUpdateInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void = { _, _, _ in } var sendMessages: ([EnqueueMessage], Bool?, Int32?, Bool) -> Void = { _, _, _, _ in } @@ -204,7 +214,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer() - self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), updatingMedia: .single([:])) self.historyNode.rotated = true self.historyNodeContainer = ASDisplayNode() self.historyNodeContainer.addSubnode(self.historyNode) @@ -546,6 +556,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let inputPanelNode = self.inputPanelNode { previousInputPanelOrigin.y -= inputPanelNode.bounds.size.height } + if let secondaryInputPanelNode = self.secondaryInputPanelNode { + previousInputPanelOrigin.y -= secondaryInputPanelNode.bounds.size.height + } self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight) var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode? @@ -567,7 +580,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var inputPanelNodeBaseHeight: CGFloat = 0.0 if let inputPanelNode = self.inputPanelNode { - inputPanelNodeBaseHeight = inputPanelNode.minimalHeight(interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + inputPanelNodeBaseHeight += inputPanelNode.minimalHeight(interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + } + if let secondaryInputPanelNode = self.secondaryInputPanelNode { + inputPanelNodeBaseHeight += secondaryInputPanelNode.minimalHeight(interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) } let maximumInputNodeHeight = layout.size.height - max(navigationBarHeight, layout.safeInsets.top) - inputPanelNodeBaseHeight @@ -579,7 +595,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { if inputPanelNode.isFocused { self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) - //inputTextPanelNode.ensureUnfocused() } } if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil { @@ -640,6 +655,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } var dismissedInputPanelNode: ASDisplayNode? + var dismissedSecondaryInputPanelNode: ASDisplayNode? var dismissedAccessoryPanelNode: ASDisplayNode? var dismissedInputContextPanelNode: ChatInputContextPanelNode? var dismissedOverlayContextPanelNode: ChatInputContextPanelNode? @@ -653,29 +669,54 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var inputPanelSize: CGSize? var immediatelyLayoutInputPanelAndAnimateAppearance = false - if let inputPanelNode = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction), !previewing { + var secondaryInputPanelSize: CGSize? + var immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = false + + let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction) + + if let inputPanelNode = inputPanelNodes.primary, !previewing { if inputPanelNode !== self.inputPanelNode { if let inputTextPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { if inputTextPanelNode.isFocused { self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) - //inputTextPanelNode.ensureUnfocused() } - let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) } dismissedInputPanelNode = self.inputPanelNode - immediatelyLayoutInputPanelAndAnimateAppearance = true - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: inputPanelNode.supernode == nil ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.inputPanelNode = inputPanelNode - self.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) + if inputPanelNode.supernode == nil { + immediatelyLayoutInputPanelAndAnimateAppearance = true + self.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) + } } else { - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { dismissedInputPanelNode = self.inputPanelNode self.inputPanelNode = nil } + + 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, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) + self.secondaryInputPanelNode = secondaryInputPanelNode + if secondaryInputPanelNode.supernode == nil { + immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = true + self.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) + } + } else { + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) + secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) + } + } else { + dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode + self.secondaryInputPanelNode = nil + } if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: false) @@ -800,8 +841,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var inputPanelsHeight: CGFloat = 0.0 var inputPanelFrame: CGRect? + var secondaryInputPanelFrame: CGRect? + if self.inputPanelNode != nil { - assert(inputPanelSize != nil) inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - inputPanelSize!.height), size: CGSize(width: layout.size.width, height: inputPanelSize!.height)) if self.dismissedAsOverlay { inputPanelFrame!.origin.y = layout.size.height @@ -809,6 +851,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { inputPanelsHeight += inputPanelSize!.height } + if self.secondaryInputPanelNode != nil { + secondaryInputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - secondaryInputPanelSize!.height), size: CGSize(width: layout.size.width, height: secondaryInputPanelSize!.height)) + if self.dismissedAsOverlay { + secondaryInputPanelFrame!.origin.y = layout.size.height + } + inputPanelsHeight += secondaryInputPanelSize!.height + } + var accessoryPanelFrame: CGRect? if self.accessoryPanelNode != nil { assert(accessoryPanelSize != nil) @@ -1041,6 +1091,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } var apparentInputPanelFrame = inputPanelFrame + var apparentSecondaryInputPanelFrame = secondaryInputPanelFrame var apparentInputBackgroundFrame = inputBackgroundFrame var apparentNavigateButtonsFrame = navigateButtonsFrame if case let .media(_, maybeExpanded) = self.chatPresentationInterfaceState.inputMode, let expanded = maybeExpanded, case .search = expanded, let inputPanelFrame = inputPanelFrame { @@ -1073,6 +1124,16 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateAlpha(node: inputPanelNode, alpha: 1.0) } + if let secondaryInputPanelNode = self.secondaryInputPanelNode, let apparentSecondaryInputPanelFrame = apparentSecondaryInputPanelFrame, !secondaryInputPanelNode.frame.equalTo(apparentSecondaryInputPanelFrame) { + if immediatelyLayoutSecondaryInputPanelAndAnimateAppearance { + secondaryInputPanelNode.frame = apparentSecondaryInputPanelFrame.offsetBy(dx: 0.0, dy: apparentSecondaryInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentSecondaryInputPanelFrame.maxY) + secondaryInputPanelNode.alpha = 0.0 + } + + transition.updateFrame(node: secondaryInputPanelNode, frame: apparentSecondaryInputPanelFrame) + transition.updateAlpha(node: secondaryInputPanelNode, alpha: 1.0) + } + if let accessoryPanelNode = self.accessoryPanelNode, let accessoryPanelFrame = accessoryPanelFrame, !accessoryPanelNode.frame.equalTo(accessoryPanelFrame) { if immediatelyLayoutAccessoryPanelAndAnimateAppearance { var startAccessoryPanelFrame = accessoryPanelFrame @@ -1147,7 +1208,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { }) } - if let dismissedInputPanelNode = dismissedInputPanelNode { + if let dismissedInputPanelNode = dismissedInputPanelNode, dismissedInputPanelNode !== self.secondaryInputPanelNode { var frameCompleted = false var alphaCompleted = false let completed = { [weak self, weak dismissedInputPanelNode] in @@ -1170,6 +1231,29 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { }) } + if let dismissedSecondaryInputPanelNode = dismissedSecondaryInputPanelNode, dismissedSecondaryInputPanelNode !== self.inputPanelNode { + var frameCompleted = false + var alphaCompleted = false + let completed = { [weak self, weak dismissedSecondaryInputPanelNode] in + if let strongSelf = self, let dismissedSecondaryInputPanelNode = dismissedSecondaryInputPanelNode, strongSelf.secondaryInputPanelNode === dismissedSecondaryInputPanelNode { + return + } + if frameCompleted && alphaCompleted { + dismissedSecondaryInputPanelNode?.removeFromSupernode() + } + } + let transitionTargetY = layout.size.height - insets.bottom + transition.updateFrame(node: dismissedSecondaryInputPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: transitionTargetY), size: dismissedSecondaryInputPanelNode.frame.size), completion: { _ in + frameCompleted = true + completed() + }) + + transition.updateAlpha(node: dismissedSecondaryInputPanelNode, alpha: 0.0, completion: { _ in + alphaCompleted = true + completed() + }) + } + if let dismissedAccessoryPanelNode = dismissedAccessoryPanelNode { var frameCompleted = false var alphaCompleted = false @@ -1450,7 +1534,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { if inputPanelNode.isFocused { self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) - //inputTextPanelNode.ensureUnfocused() } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatEditMessageMediaContext.swift b/submodules/TelegramUI/TelegramUI/ChatEditMessageMediaContext.swift new file mode 100644 index 0000000000..a20222c70b --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/ChatEditMessageMediaContext.swift @@ -0,0 +1,32 @@ +import Foundation +import SwiftSignalKit +import Postbox +import SyncCore +import TelegramCore +import AccountContext + +private final class MessageContext { + let disposable: Disposable + + init(disposable: Disposable) { + self.disposable = disposable + } + + deinit { + self.disposable.dispose() + } +} + +final class ChatEditMessageMediaContext { + private let context: AccountContext + + private let contexts: [MessageId: MessageContext] = [:] + + init(context: AccountContext) { + self.context = context + } + + func update(id: MessageId, text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, media: RequestEditMessageMedia) { + + } +} diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift index 7aaa8cee48..f034e0b8ef 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift @@ -446,7 +446,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var loadedMessagesFromCachedDataDisposable: Disposable? - public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { + public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, updatingMedia: Signal<[MessageId: ChatUpdatingMessageMedia], NoError>, mode: ChatHistoryListMode = .bubbles) { self.context = context self.chatLocation = chatLocation self.subject = subject @@ -575,10 +575,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { historyViewUpdate, self.chatPresentationDataPromise.get(), selectedMessages, + updatingMedia, automaticDownloadNetworkType, self.historyAppearsClearedPromise.get(), animatedEmojiStickers - ).start(next: { [weak self] update, chatPresentationData, selectedMessages, networkType, historyAppearsCleared, animatedEmojiStickers in + ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, animatedEmojiStickers in func applyHole() { Queue.mainQueue().async { if let strongSelf = self { diff --git a/submodules/TelegramUI/TelegramUI/ChatUpdatingMessageMedia.swift b/submodules/TelegramUI/TelegramUI/ChatUpdatingMessageMedia.swift new file mode 100644 index 0000000000..20bcad8fea --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/ChatUpdatingMessageMedia.swift @@ -0,0 +1,39 @@ +import Foundation +import Postbox +import SyncCore +import TelegramCore + +public final class ChatUpdatingMessageMedia: Equatable { + public let text: String + public let entities: TextEntitiesMessageAttribute? + public let disableUrlPreview: Bool + public let media: RequestEditMessageMedia + public let progress: Float + + init(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, media: RequestEditMessageMedia, progress: Float) { + self.text = text + self.entities = entities + self.disableUrlPreview = disableUrlPreview + self.media = media + self.progress = progress + } + + public static func ==(lhs: ChatUpdatingMessageMedia, rhs: ChatUpdatingMessageMedia) -> Bool { + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + if lhs.disableUrlPreview != rhs.disableUrlPreview { + return false + } + if lhs.media != rhs.media { + return false + } + if lhs.progress != rhs.progress { + return false + } + return true + } +} diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionControllerNode.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionControllerNode.swift index 8097b052c7..008d2ef563 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionControllerNode.swift @@ -29,7 +29,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: Ac } return node case .file: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false)) + let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, updatingMedia: .single([:]), mode: .list(search: true, reversed: false)) node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor node.didEndScrolling = { [weak node] in guard let node = node else { @@ -40,7 +40,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: Ac node.preloadPages = true return node case .music: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false)) + let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, updatingMedia: .single([:]), mode: .list(search: true, reversed: false)) node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor node.didEndScrolling = { [weak node] in guard let node = node else { @@ -51,7 +51,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: Ac node.preloadPages = true return node case .webpage: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false)) + let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, updatingMedia: .single([:]), mode: .list(search: true, reversed: false)) node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor node.didEndScrolling = { [weak node] in guard let node = node else {