[WIP] non-modal message editing

This commit is contained in:
Ali 2019-11-12 17:53:01 +04:00
parent 433d5c8ed9
commit cec0770a3a
5 changed files with 173 additions and 18 deletions

View File

@ -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()
}
}
}

View File

@ -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) {
}
}

View File

@ -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<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles) {
public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, 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 {

View File

@ -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
}
}

View File

@ -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 {