From 4aada91da589d9792945ba3190b480cebb384e25 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 8 Sep 2020 16:07:35 +0300 Subject: [PATCH] Search filters improvements --- submodules/ChatInterfaceState/BUCK | 22 ++ submodules/ChatInterfaceState/BUILD | 22 ++ .../Sources/ChatInterfaceState.swift | 129 +++++---- submodules/ChatListUI/BUCK | 1 + submodules/ChatListUI/BUILD | 2 + .../Sources/ChatListSearchContainerNode.swift | 267 ++++++++++++------ .../Sources/ChatListSearchMediaNode.swift | 149 ++++++---- ...tListSearchMessageSelectionPanelNode.swift | 181 ++++++++++++ .../Sources/ListMessageItem.swift | 14 +- .../SearchBarNode/Sources/SearchBarNode.swift | 6 + .../Sources/DefaultDayPresentationTheme.swift | 8 +- submodules/TelegramUI/BUCK | 1 + submodules/TelegramUI/BUILD | 1 + .../Sources/AudioWaveformNode.swift | 3 - .../TelegramUI/Sources/ChatController.swift | 1 + .../Sources/ChatControllerInteraction.swift | 1 + .../Sources/ChatControllerNode.swift | 1 + .../Sources/ChatHistoryListNode.swift | 22 +- .../Sources/ChatHistoryViewForLocation.swift | 1 + .../Sources/ChatInterfaceInputContexts.swift | 1 + .../Sources/ChatMediaInputNode.swift | 1 + .../ChatPresentationInterfaceState.swift | 1 + .../ChatTextInputMediaRecordingButton.swift | 6 +- .../Sources/DeclareEncodables.swift | 1 + .../TelegramUI/Sources/OpenResolvedUrl.swift | 1 + .../Sources/OverlayPlayerControllerNode.swift | 4 +- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 1 + .../PeerMediaCollectionInterfaceState.swift | 1 + .../TelegramAccountAuxiliaryMethods.swift | 1 + 30 files changed, 614 insertions(+), 238 deletions(-) create mode 100644 submodules/ChatInterfaceState/BUCK create mode 100644 submodules/ChatInterfaceState/BUILD rename submodules/{TelegramUI => ChatInterfaceState}/Sources/ChatInterfaceState.swift (84%) create mode 100644 submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift diff --git a/submodules/ChatInterfaceState/BUCK b/submodules/ChatInterfaceState/BUCK new file mode 100644 index 0000000000..13b14d8c7b --- /dev/null +++ b/submodules/ChatInterfaceState/BUCK @@ -0,0 +1,22 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "ChatInterfaceState", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/SyncCore:SyncCore#shared", + "//submodules/TextFormat:TextFormat", + "//submodules/AccountContext:AccountContext", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/ChatInterfaceState/BUILD b/submodules/ChatInterfaceState/BUILD new file mode 100644 index 0000000000..08daa544b5 --- /dev/null +++ b/submodules/ChatInterfaceState/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInterfaceState", + module_name = "ChatInterfaceState", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TextFormat:TextFormat", + "//submodules/AccountContext:AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatInterfaceState.swift rename to submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index 2de35faf20..bcc798afd9 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -6,18 +6,23 @@ import SyncCore import TextFormat import AccountContext -struct ChatInterfaceSelectionState: PostboxCoding, Equatable { - let selectedIds: Set +public enum ChatTextInputMediaRecordingButtonMode: Int32 { + case audio = 0 + case video = 1 +} + +public struct ChatInterfaceSelectionState: PostboxCoding, Equatable { + public let selectedIds: Set - static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { + public static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { return lhs.selectedIds == rhs.selectedIds } - init(selectedIds: Set) { + public init(selectedIds: Set) { self.selectedIds = selectedIds } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { if let data = decoder.decodeBytesForKeyNoCopy("i") { self.selectedIds = Set(MessageId.decodeArrayFromBuffer(data)) } else { @@ -25,27 +30,27 @@ struct ChatInterfaceSelectionState: PostboxCoding, Equatable { } } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { let buffer = WriteBuffer() MessageId.encodeArrayToBuffer(Array(selectedIds), buffer: buffer) encoder.encodeBytes(buffer, forKey: "i") } } -struct ChatEditMessageState: PostboxCoding, Equatable { - let messageId: MessageId - let inputState: ChatTextInputState - let disableUrlPreview: String? - let inputTextMaxLength: Int32? +public struct ChatEditMessageState: PostboxCoding, Equatable { + public let messageId: MessageId + public let inputState: ChatTextInputState + public let disableUrlPreview: String? + public let inputTextMaxLength: Int32? - init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) { + public init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) { self.messageId = messageId self.inputState = inputState self.disableUrlPreview = disableUrlPreview self.inputTextMaxLength = inputTextMaxLength } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("mp", orElse: 0)), namespace: decoder.decodeInt32ForKey("mn", orElse: 0), id: decoder.decodeInt32ForKey("mi", orElse: 0)) if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState { self.inputState = inputState @@ -56,7 +61,7 @@ struct ChatEditMessageState: PostboxCoding, Equatable { self.inputTextMaxLength = decoder.decodeOptionalInt32ForKey("tl") } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "mp") encoder.encodeInt32(self.messageId.namespace, forKey: "mn") encoder.encodeInt32(self.messageId.id, forKey: "mi") @@ -73,31 +78,31 @@ struct ChatEditMessageState: PostboxCoding, Equatable { } } - static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { + public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreview == rhs.disableUrlPreview && lhs.inputTextMaxLength == rhs.inputTextMaxLength } - func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { + public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreview: self.disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength) } - func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState { + public func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreview: disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength) } } -struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { - var closedButtonKeyboardMessageId: MessageId? - var processedSetupReplyMessageId: MessageId? - var closedPinnedMessageId: MessageId? - var closedPeerSpecificPackSetup: Bool = false - var dismissedAddContactPhoneNumber: String? +public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { + public var closedButtonKeyboardMessageId: MessageId? + public var processedSetupReplyMessageId: MessageId? + public var closedPinnedMessageId: MessageId? + public var closedPeerSpecificPackSetup: Bool = false + public var dismissedAddContactPhoneNumber: String? - var isEmpty: Bool { + public var isEmpty: Bool { return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil } - init() { + public init() { self.closedButtonKeyboardMessageId = nil self.processedSetupReplyMessageId = nil self.closedPinnedMessageId = nil @@ -105,7 +110,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.dismissedAddContactPhoneNumber = nil } - init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { + public init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId self.processedSetupReplyMessageId = processedSetupReplyMessageId self.closedPinnedMessageId = closedPinnedMessageId @@ -113,7 +118,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.dismissedAddContactPhoneNumber = dismissedAddContactPhoneNumber } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { if let closedMessageIdPeerId = decoder.decodeOptionalInt64ForKey("cb.p"), let closedMessageIdNamespace = decoder.decodeOptionalInt32ForKey("cb.n"), let closedMessageIdId = decoder.decodeOptionalInt32ForKey("cb.i") { self.closedButtonKeyboardMessageId = MessageId(peerId: PeerId(closedMessageIdPeerId), namespace: closedMessageIdNamespace, id: closedMessageIdId) } else { @@ -135,7 +140,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.closedPeerSpecificPackSetup = decoder.decodeInt32ForKey("cpss", orElse: 0) != 0 } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { if let closedButtonKeyboardMessageId = self.closedButtonKeyboardMessageId { encoder.encodeInt64(closedButtonKeyboardMessageId.peerId.toInt64(), forKey: "cb.p") encoder.encodeInt32(closedButtonKeyboardMessageId.namespace, forKey: "cb.n") @@ -176,21 +181,21 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { } } -struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { - let messageIndex: MessageIndex - let relativeOffset: Double +public struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { + public let messageIndex: MessageIndex + public let relativeOffset: Double - init(messageIndex: MessageIndex, relativeOffset: Double) { + public init(messageIndex: MessageIndex, relativeOffset: Double) { self.messageIndex = messageIndex self.relativeOffset = relativeOffset } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { self.messageIndex = MessageIndex(id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0)), timestamp: decoder.decodeInt32ForKey("m.t", orElse: 0)) self.relativeOffset = decoder.decodeDoubleForKey("ro", orElse: 0.0) } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.messageIndex.timestamp, forKey: "m.t") encoder.encodeInt64(self.messageIndex.id.peerId.toInt64(), forKey: "m.p") encoder.encodeInt32(self.messageIndex.id.namespace, forKey: "m.n") @@ -198,7 +203,7 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { encoder.encodeDouble(self.relativeOffset, forKey: "ro") } - static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { + public static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { if lhs.messageIndex != rhs.messageIndex { return false } @@ -210,18 +215,18 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { } public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { - let timestamp: Int32 - let composeInputState: ChatTextInputState - let composeDisableUrlPreview: String? - let replyMessageId: MessageId? - let forwardMessageIds: [MessageId]? - let editMessage: ChatEditMessageState? - let selectionState: ChatInterfaceSelectionState? - let messageActionsState: ChatInterfaceMessageActionsState - let historyScrollState: ChatInterfaceHistoryScrollState? - let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode - let silentPosting: Bool - let inputLanguage: String? + public let timestamp: Int32 + public let composeInputState: ChatTextInputState + public let composeDisableUrlPreview: String? + public let replyMessageId: MessageId? + public let forwardMessageIds: [MessageId]? + public let editMessage: ChatEditMessageState? + public let selectionState: ChatInterfaceSelectionState? + public let messageActionsState: ChatInterfaceMessageActionsState + public let historyScrollState: ChatInterfaceHistoryScrollState? + public let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode + public let silentPosting: Bool + public let inputLanguage: String? public var associatedMessageIds: [MessageId] { var ids: [MessageId] = [] @@ -259,7 +264,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return result } - var effectiveInputState: ChatTextInputState { + public var effectiveInputState: ChatTextInputState { if let editMessage = self.editMessage { return editMessage.inputState } else { @@ -282,7 +287,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata self.inputLanguage = nil } - init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { + public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreview = composeDisableUrlPreview @@ -437,17 +442,17 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return lhs.composeInputState == rhs.composeInputState && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState && lhs.editMessage == rhs.editMessage } - func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { + public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { + public func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { + public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { var updatedEditMessage = self.editMessage var updatedComposeInputState = self.composeInputState if let editMessage = self.editMessage { @@ -459,15 +464,15 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { + public func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState { + public func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState { + public func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) @@ -478,7 +483,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState { + public func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) @@ -493,27 +498,27 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withoutSelectionState() -> ChatInterfaceState { + public func withoutSelectionState() -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { + public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { + public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { + public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { + public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { + public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } diff --git a/submodules/ChatListUI/BUCK b/submodules/ChatListUI/BUCK index c37f40e0f9..4856df0789 100644 --- a/submodules/ChatListUI/BUCK +++ b/submodules/ChatListUI/BUCK @@ -53,6 +53,7 @@ static_library( "//submodules/GalleryData:GalleryData", "//submodules/InstantPageUI:InstantPageUI", "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/ChatInterfaceState:ChatInterfaceState", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index e34ef99e1e..5979bedc52 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -52,6 +52,8 @@ swift_library( "//submodules/GalleryData:GalleryData", "//submodules/InstantPageUI:InstantPageUI", "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/ChatInterfaceState:ChatInterfaceState", + "//submodules/ShareController:ShareController", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index d33d2887a8..d4a4617e7f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -27,6 +27,8 @@ import AnimatedStickerNode import AppBundle import GalleryData import InstantPageUI +import ChatInterfaceState +import ShareController private final class PassthroughContainerNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -595,20 +597,26 @@ public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatLis private struct ChatListSearchContainerNodeState: Equatable { let peerIdWithRevealedOptions: PeerId? + let selectedMessageIds: Set? - init(peerIdWithRevealedOptions: PeerId? = nil) { + init(peerIdWithRevealedOptions: PeerId? = nil, selectedMessageIds: Set? = nil) { self.peerIdWithRevealedOptions = peerIdWithRevealedOptions + self.selectedMessageIds = selectedMessageIds } static func ==(lhs: ChatListSearchContainerNodeState, rhs: ChatListSearchContainerNodeState) -> Bool { - if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions { + if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions || lhs.selectedMessageIds != rhs.selectedMessageIds { return false } return true } func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChatListSearchContainerNodeState { - return ChatListSearchContainerNodeState(peerIdWithRevealedOptions: peerIdWithRevealedOptions) + return ChatListSearchContainerNodeState(peerIdWithRevealedOptions: peerIdWithRevealedOptions, selectedMessageIds: self.selectedMessageIds) + } + + func withUpdatedSelectedMessageIds(_ selectedMessageIds: Set?) -> ChatListSearchContainerNodeState { + return ChatListSearchContainerNodeState(peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, selectedMessageIds: selectedMessageIds) } } @@ -681,12 +689,13 @@ public struct ChatListSearchOptions { public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext - private let filter: ChatListNodePeersFilter + private let peersFilter: ChatListNodePeersFilter private var interaction: ChatListNodeInteraction? private let openMessage: (Peer, MessageId) -> Void private let navigationController: NavigationController? let filterContainerNode: ChatListSearchFiltersContainerNode + private var selectionPanelNode: ChatListSearchMessageSelectionPanelNode? private let recentListNode: ListView private let listNode: ListView private let mediaNode: ChatListSearchMediaNode @@ -695,8 +704,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = [] private var validLayout: (ContainerViewLayout, CGFloat)? + private var present: ((ViewController, Any?) -> Void)? private var presentInGlobalOverlay: ((ViewController, Any?) -> Void)? + private let activeActionDisposable = MetaDisposable() + private let recentDisposable = MetaDisposable() private let updatedRecentPeersDisposable = MetaDisposable() @@ -738,11 +750,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)? = nil) { self.context = context - self.filter = filter + self.peersFilter = filter self.dimNode = ASDisplayNode() self.navigationController = navigationController self.updatedSearchOptions = updatedSearchOptions + self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay self.openMessage = originalOpenMessage @@ -1475,7 +1488,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } } - strongSelf.mediaNode.updateHistory(entries: entriesAndFlags?.0 ?? [], totalCount: totalCount, updateType: .Initial) + var entries: [ChatListSearchEntry]? = entriesAndFlags?.0 ?? [] + if isSearching && (entries?.isEmpty ?? true) { + entries = nil + } + strongSelf.mediaNode.updateHistory(entries: entries, totalCount: totalCount, updateType: .Initial) } let previousEntries = previousSearchItems.swap(entriesAndFlags?.0) @@ -1621,6 +1638,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } deinit { + self.activeActionDisposable.dispose() self.updatedRecentPeersDisposable.dispose() self.recentDisposable.dispose() self.searchDisposable.dispose() @@ -1701,8 +1719,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } private func updateTheme(theme: PresentationTheme) { - self.backgroundColor = self.filter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor - self.dimNode.backgroundColor = self.filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor + self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor + self.dimNode.backgroundColor = self.peersFilter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor self.listNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor @@ -1811,9 +1829,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo strongSelf.emptyResultsTextNode.isHidden = !emptyResults strongSelf.emptyResultsAnimationNode.visibility = emptyResults - strongSelf.recentListNode.isHidden = displayingResults || strongSelf.filter.contains(.excludeRecent) + strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent) strongSelf.dimNode.isHidden = displayingResults - strongSelf.backgroundColor = !displayingResults && strongSelf.filter.contains(.excludeRecent) ? nil : strongSelf.presentationData.theme.chatList.backgroundColor + strongSelf.backgroundColor = !displayingResults && strongSelf.peersFilter.contains(.excludeRecent) ? nil : strongSelf.presentationData.theme.chatList.backgroundColor if let (layout, navigationBarHeight) = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) @@ -2036,6 +2054,66 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 37.0))) self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 37.0), sideInset: layout.safeInsets.left, filters: ChatListSearchFilter.allCases.map { .filter($0) }, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + + if let selectedMessageIds = self.stateValue.selectedMessageIds { + var wasAdded = false + let selectionPanelNode: ChatListSearchMessageSelectionPanelNode + if let current = self.selectionPanelNode { + selectionPanelNode = current + } else { + wasAdded = true + selectionPanelNode = ChatListSearchMessageSelectionPanelNode(context: self.context, deleteMessages: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.deleteMessages(messageIds: nil) + }, shareMessages: { [weak self] in + guard let strongSelf = self, let messageIds = strongSelf.stateValue.selectedMessageIds, !messageIds.isEmpty else { + return + } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in + var messages: [Message] = [] + for id in messageIds { + if let message = transaction.getMessage(id) { + messages.append(message) + } + } + return messages + } + |> deliverOnMainQueue).start(next: { messages in + if let strongSelf = self, !messages.isEmpty { + let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in + return lhs.index < rhs.index + })), externalShare: true, immediateExternalShare: true) + strongSelf.view.endEditing(true) + strongSelf.present?(shareController, nil) + } + }) + }, forwardMessages: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.forwardMessages(messageIds: nil) + }) + self.selectionPanelNode = selectionPanelNode + self.addSubnode(selectionPanelNode) + } + selectionPanelNode.selectedMessages = selectedMessageIds + let panelHeight = selectionPanelNode.update(layout: layout, presentationData: self.presentationData, transition: wasAdded ? .immediate : transition) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)) + if wasAdded { + selectionPanelNode.frame = panelFrame + transition.animatePositionAdditive(node: selectionPanelNode, offset: CGPoint(x: 0.0, y: panelHeight)) + } else { + transition.updateFrame(node: selectionPanelNode, frame: panelFrame) + } + } else if let selectionPanelNode = self.selectionPanelNode { + self.selectionPanelNode = nil + transition.updateFrame(node: selectionPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: selectionPanelNode.bounds.size), completion: { [weak selectionPanelNode] _ in + selectionPanelNode?.removeFromSupernode() + }) + } + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) self.recentListNode.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -2180,15 +2258,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - guard let strongSelf = self else { - return + if let strongSelf = self { + strongSelf.updateState { state in + return state.withUpdatedSelectedMessageIds([message.id]) + } + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } } -// strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true) -// strongSelf.expandTabs() + f(.default) }))) - -// } switch previewData { case let .gallery(gallery): @@ -2245,12 +2326,17 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }))) items.append(.separator) - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c.dismiss(completion: { -// if let strongSelf = self { -// strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true) -// strongSelf.expandTabs() -// } + if let strongSelf = self { + strongSelf.updateState { state in + return state.withUpdatedSelectedMessageIds([message.id]) + } + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } }) }))) @@ -2262,74 +2348,83 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.updateSearchOptions(nil) self.setQuery?(nil, [], self.searchQueryValue ?? "") } + func deleteMessages(messageIds: Set?) { + + } func forwardMessages(messageIds: Set?) { -// if let messageIds = messageIds, !messageIds.isEmpty { -// let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled])) -// peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in -// if let strongSelf = self, let _ = peerSelectionController { -// if peerId == strongSelf.context.account.peerId { -//// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone) -// -// let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in -// return .forward(source: id, grouping: .auto, attributes: []) -// }) -// |> deliverOnMainQueue).start(next: { [weak self] messageIds in -// if let strongSelf = self { -// let signals: [Signal] = messageIds.compactMap({ id -> Signal? in -// guard let id = id else { -// return nil -// } -// return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) -// |> mapToSignal { status, _ -> Signal in -// if status != nil { -// return .never() -// } else { -// return .single(true) -// } -// } -// |> take(1) -// }) -// strongSelf.activeActionDisposable.set((combineLatest(signals) -// |> deliverOnMainQueue).start(completed: { -// guard let strongSelf = self else { -// return -// } -// strongSelf.controller?.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), in: .window(.root)) -// })) -// } -// }) -// if let peerSelectionController = peerSelectionController { -// peerSelectionController.dismiss() -// } -// } else { -// let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in -// transaction.updatePeerChatInterfaceState(peerId, update: { currentState in -// if let currentState = currentState as? ChatInterfaceState { -// return currentState.withUpdatedForwardMessageIds(Array(messageIds)) -// } else { -// return ChatInterfaceState().withUpdatedForwardMessageIds(Array(messageIds)) -// } -// }) -// }) |> deliverOnMainQueue).start(completed: { -// if let strongSelf = self { + if let messageIds = messageIds, !messageIds.isEmpty { + let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled])) + peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in + if let strongSelf = self, let _ = peerSelectionController { + if peerId == strongSelf.context.account.peerId { +// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone) + + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in + return .forward(source: id, grouping: .auto, attributes: []) + }) + |> deliverOnMainQueue).start(next: { [weak self] messageIds in + if let strongSelf = self { + let signals: [Signal] = messageIds.compactMap({ id -> Signal? in + guard let id = id else { + return nil + } + return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) + }) + strongSelf.activeActionDisposable.set((combineLatest(signals) + |> deliverOnMainQueue).start(completed: { + guard let strongSelf = self else { + return + } + strongSelf.present?(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), nil) + })) + } + }) + if let peerSelectionController = peerSelectionController { + peerSelectionController.dismiss() + } + } else { + let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { currentState in + if let currentState = currentState as? ChatInterfaceState { + return currentState.withUpdatedForwardMessageIds(Array(messageIds)) + } else { + return ChatInterfaceState().withUpdatedForwardMessageIds(Array(messageIds)) + } + }) + }) |> deliverOnMainQueue).start(completed: { + if let strongSelf = self { // strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone) -// -// let ready = Promise() -// strongSelf.activeActionDisposable.set((ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in -// if let peerSelectionController = peerSelectionController { -// peerSelectionController.dismiss() -// } -// })) -// -// (strongSelf.controller?.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: false, ready: ready) -// } -// }) -// } -// } -// } -// self.controller?.push(peerSelectionController) -// } + + let ready = Promise() + let signal = ready.get() + |> filter({ $0 }) + |> take(1) + |> deliverOnMainQueue + + strongSelf.activeActionDisposable.set(signal.start(next: { _ in + if let peerSelectionController = peerSelectionController { + peerSelectionController.dismiss() + } + })) + + let controller = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) + strongSelf.navigationController?.replaceTopController(controller, animated: false, ready: ready) + } + }) + } + } + } + self.navigationController?.pushViewController(peerSelectionController) + } } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift index 6e2ef5d1bc..7368635594 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift @@ -14,6 +14,7 @@ import UniversalMediaPlayer import ListMessageItem import ListSectionHeaderNode import ChatMessageInteractiveMediaBadge +import ShimmerEffect private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white @@ -41,8 +42,6 @@ private final class VisualMediaItemNode: ASDisplayNode { private let context: AccountContext private let interaction: VisualMediaItemInteraction -// private var videoLayerFrameManager: SoftwareVideoLayerFrameManager? -// private var sampleBufferLayer: SampleBufferLayer? private var displayLink: ConstantDisplayLinkAnimator? private var displayLinkTimestamp: Double = 0.0 @@ -50,6 +49,7 @@ private final class VisualMediaItemNode: ASDisplayNode { private let imageNode: TransformImageNode private var statusNode: RadialStatusNode private let mediaBadgeNode: ChatMessageInteractiveMediaBadge + private var placeholderNode: ShimmerEffectNode? private let fetchStatusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() @@ -84,7 +84,9 @@ private final class VisualMediaItemNode: ASDisplayNode { guard let strongSelf = self, let item = strongSelf.item else { return } - strongSelf.interaction.openMessageContextActions(item.0.message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) + if let message = item.0.message { + strongSelf.interaction.openMessageContextActions(message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) + } } } @@ -108,12 +110,15 @@ private final class VisualMediaItemNode: ASDisplayNode { } @objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + guard let message = self.item?.0.message else { + return + } if case .ended = recognizer.state { if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { if case .tap = gesture { if let (item, _, _, _) = self.item { var media: Media? - for value in item.message.media { + for value in message.media { if let image = value as? TelegramMediaImage { media = image break @@ -125,13 +130,13 @@ private final class VisualMediaItemNode: ASDisplayNode { if let media = media { if let file = media as? TelegramMediaFile { - if isMediaStreamable(message: item.message, media: file) { - self.interaction.openMessage(item.message) + if isMediaStreamable(message: message, media: file) { + self.interaction.openMessage(message) } else { self.progressPressed() } } else { - self.interaction.openMessage(item.message) + self.interaction.openMessage(message) } } } @@ -172,50 +177,34 @@ private final class VisualMediaItemNode: ASDisplayNode { self.containerNode.cancelGesture() } + func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { + self.placeholderNode?.updateAbsoluteRect(absoluteRect, within: containerSize) + } + func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) { if item === self.item?.0 && size == self.item?.2 { return } self.theme = theme var media: Media? - for value in item.message.media { - if let image = value as? TelegramMediaImage { - media = image - break - } else if let file = value as? TelegramMediaFile { - media = file - break + if let message = item.message { + for value in message.media { + if let image = value as? TelegramMediaImage { + media = image + break + } else if let file = value as? TelegramMediaFile { + media = file + break + } } } -// if let file = media as? TelegramMediaFile, file.isAnimated { -// if self.videoLayerFrameManager == nil { -// let sampleBufferLayer: SampleBufferLayer -// if let current = self.sampleBufferLayer { -// sampleBufferLayer = current -// } else { -// sampleBufferLayer = takeSampleBufferLayer() -// self.sampleBufferLayer = sampleBufferLayer -// self.imageNode.layer.addSublayer(sampleBufferLayer.layer) -// } -// -// self.videoLayerFrameManager = SoftwareVideoLayerFrameManager(account: self.context.account, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), layerHolder: sampleBufferLayer) -// self.videoLayerFrameManager?.start() -// } -// } else { -// if let sampleBufferLayer = self.sampleBufferLayer { -// sampleBufferLayer.layer.removeFromSuperlayer() -// self.sampleBufferLayer = nil -// } -// self.videoLayerFrameManager = nil -// } - - if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) { + if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)), let message = item.message { var mediaDimensions: CGSize? if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions { mediaDimensions = largestSize.cgSize - self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(item.message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) + self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) self.fetchStatusDisposable.set(nil) self.statusNode.transitionToState(.none, completion: { [weak self] in @@ -225,7 +214,7 @@ private final class VisualMediaItemNode: ASDisplayNode { self.resourceStatus = nil } else if let file = media as? TelegramMediaFile, file.isVideo { mediaDimensions = file.dimensions?.cgSize - self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(item.message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) + self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) self.mediaBadgeNode.isHidden = file.isAnimated @@ -233,12 +222,12 @@ private final class VisualMediaItemNode: ASDisplayNode { self.item = (item, media, size, mediaDimensions) - self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: item.message.id, file: file) + self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: message.id, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self, let (item, _, _, _) = strongSelf.item { strongSelf.resourceStatus = status - let isStreamable = isMediaStreamable(message: item.message, media: file) + let isStreamable = isMediaStreamable(message: message, media: file) var statusState: RadialStatusNodeState = .none if isStreamable || file.isAnimated { @@ -310,18 +299,26 @@ private final class VisualMediaItemNode: ASDisplayNode { self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 18.0 - 3.0), size: CGSize(width: 50.0, height: 50.0)) self.updateHiddenMedia() + + if let placeholderNode = self.placeholderNode { + self.placeholderNode = nil + placeholderNode.removeFromSupernode() + } + } else if item.isEmpty, self.placeholderNode == nil { + let placeholderNode = ShimmerEffectNode() + placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: size) + self.addSubnode(placeholderNode) + self.placeholderNode = placeholderNode } + let imageFrame = CGRect(origin: CGPoint(), size: size) + self.placeholderNode?.frame = imageFrame + if let (item, media, _, mediaDimensions) = self.item { self.item = (item, media, size, mediaDimensions) - let imageFrame = CGRect(origin: CGPoint(), size: size) - self.containerNode.frame = imageFrame self.imageNode.frame = imageFrame -// if let sampleBufferLayer = self.sampleBufferLayer { -// sampleBufferLayer.layer.frame = imageFrame -// } if let mediaDimensions = mediaDimensions { let imageSize = mediaDimensions.aspectFilled(imageFrame.size) @@ -379,8 +376,8 @@ private final class VisualMediaItemNode: ASDisplayNode { } func updateHiddenMedia() { - if let (item, _, _, _) = self.item { - if let _ = self.interaction.hiddenMedia[item.message.id] { + if let (item, _, _, _) = self.item, let message = item.message { + if let _ = self.interaction.hiddenMedia[message.id] { self.isHidden = true } else { self.isHidden = false @@ -393,11 +390,22 @@ private final class VisualMediaItemNode: ASDisplayNode { } private final class VisualMediaItem { - let message: Message + let index: UInt32? + let message: Message? let dimensions: CGSize let aspectRatio: CGFloat + let isEmpty: Bool + + init(index: UInt32) { + self.index = index + self.message = nil + self.dimensions = CGSize(width: 100.0, height: 100.0) + self.aspectRatio = 1.0 + self.isEmpty = true + } init(message: Message) { + self.index = nil self.message = message var aspectRatio: CGFloat = 1.0 @@ -412,6 +420,17 @@ private final class VisualMediaItem { } self.aspectRatio = aspectRatio self.dimensions = dimensions + self.isEmpty = false + } + + var stableId: UInt32 { + if let message = self.message { + return message.stableId + } else if let index = self.index { + return index + } else { + return 0 + } } } @@ -680,15 +699,24 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { } - func updateHistory(entries: [ChatListSearchEntry], totalCount: Int32, updateType: ViewUpdateType) { + func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) { switch updateType { case .FillHole: break default: self.mediaItems.removeAll() - for entry in entries { - if case let .message(message, _, _, _, _) = entry { - self.mediaItems.append(VisualMediaItem(message: message)) + let loading: Bool + if let entries = entries { + loading = false + for entry in entries { + if case let .message(message, _, _, _, _) = entry { + self.mediaItems.append(VisualMediaItem(message: message)) + } + } + } else { + loading = true + for i in 0 ..< 21 { + self.mediaItems.append(VisualMediaItem(index: UInt32(i))) } } self.itemsLayout = nil @@ -697,12 +725,14 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { self.initialized = true if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { - if totalCount > 0 { + if loading { + self.headerNode.title = "" + } else if totalCount > 0 { self.headerNode.title = presentationData.strings.ChatList_Search_Photos(totalCount).uppercased() } self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate) - self.headerNode.alpha = self.mediaItems.isEmpty ? 0.0 : 1.0 + self.headerNode.alpha = self.mediaItems.isEmpty && !loading ? 0.0 : 1.0 if !self.didSetReady { self.didSetReady = true self.ready.set(.single(true)) @@ -722,7 +752,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { func findLoadedMessage(id: MessageId) -> Message? { for item in self.mediaItems { - if item.message.id == id { + if item.message?.id == id { return item.message } } @@ -743,8 +773,8 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { for item in self.mediaItems { - if item.message.id == messageId { - if let itemNode = self.visibleMediaItems[item.message.stableId] { + if let message = item.message, message.id == messageId { + if let itemNode = self.visibleMediaItems[message.stableId] { return itemNode.transitionNode() } break @@ -855,7 +885,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { var validIds = Set() if minVisibleIndex <= maxVisibleIndex { for i in minVisibleIndex ... maxVisibleIndex { - let stableId = self.mediaItems[i].message.stableId + let stableId = self.mediaItems[i].stableId validIds.insert(stableId) let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset) @@ -869,6 +899,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { self.scrollNode.addSubnode(itemNode) } itemNode.frame = itemFrame + itemNode.updateAbsoluteRect(itemFrame, within: self.scrollNode.view.bounds.size) if headerItem == nil && itemFrame.maxY > headerItemMinY { headerItem = self.mediaItems[i].message } diff --git a/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift new file mode 100644 index 0000000000..556478f05f --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift @@ -0,0 +1,181 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import AppBundle + +final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode { + private let context: AccountContext + private var theme: PresentationTheme + + private let deleteMessages: () -> Void + private let shareMessages: () -> Void + private let forwardMessages: () -> Void + + private let separatorNode: ASDisplayNode + private let backgroundNode: ASDisplayNode + private let deleteButton: HighlightableButtonNode + private let forwardButton: HighlightableButtonNode + private let shareButton: HighlightableButtonNode + + private var actions: ChatAvailableMessageActions? + + private let canDeleteMessagesDisposable = MetaDisposable() + + private var validLayout: ContainerViewLayout? + + var selectedMessages = Set() { + didSet { + if oldValue != self.selectedMessages { + self.forwardButton.isEnabled = self.selectedMessages.count != 0 + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if self.selectedMessages.isEmpty { + self.actions = nil + if let layout = self.validLayout { + let _ = self.update(layout: layout, presentationData: presentationData, transition: .immediate) + } + self.canDeleteMessagesDisposable.set(nil) + } else { + self.canDeleteMessagesDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: self.selectedMessages) + |> deliverOnMainQueue).start(next: { [weak self] actions in + if let strongSelf = self { + strongSelf.actions = actions + if let layout = strongSelf.validLayout { + let _ = strongSelf.update(layout: layout, presentationData: presentationData, transition: .immediate) + } + } + })) + } + } + } + } + + init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void) { + self.context = context + self.deleteMessages = deleteMessages + self.shareMessages = shareMessages + self.forwardMessages = forwardMessages + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.theme = presentationData.theme + + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = presentationData.theme.chat.inputPanel.panelSeparatorColor + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = presentationData.theme.chat.inputPanel.panelBackgroundColor + + self.deleteButton = HighlightableButtonNode(pointerStyle: .default) + self.deleteButton.isEnabled = false + self.deleteButton.isAccessibilityElement = true + self.deleteButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextDelete + + self.forwardButton = HighlightableButtonNode(pointerStyle: .default) + self.forwardButton.isAccessibilityElement = true + self.forwardButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextForward + + self.shareButton = HighlightableButtonNode(pointerStyle: .default) + self.shareButton.isEnabled = false + self.shareButton.isAccessibilityElement = true + self.shareButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextShare + + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + + super.init() + + self.addSubnode(self.deleteButton) + self.addSubnode(self.forwardButton) + self.addSubnode(self.shareButton) + self.addSubnode(self.separatorNode) + + self.forwardButton.isEnabled = false + + self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside) + self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside) + self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), forControlEvents: .touchUpInside) + } + + deinit { + self.canDeleteMessagesDisposable.dispose() + } + + func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { + if presentationData.theme !== self.theme { + self.theme = presentationData.theme + + self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor + self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor + + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + } + + let width = layout.size.width + let insets = layout.insets(options: []) + let leftInset = insets.left + let rightInset = insets.left + + let panelHeight: CGFloat + if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { + panelHeight = 49.0 + } else { + panelHeight = 45.0 + } + + if let actions = self.actions { + self.deleteButton.isEnabled = false + self.forwardButton.isEnabled = actions.options.contains(.forward) + self.shareButton.isEnabled = false + + self.deleteButton.isEnabled = !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty + self.shareButton.isEnabled = !actions.options.intersection([.forward]).isEmpty + + self.deleteButton.isHidden = !self.deleteButton.isEnabled + } else { + self.deleteButton.isEnabled = false + self.deleteButton.isHidden = true + self.forwardButton.isEnabled = false + self.shareButton.isEnabled = false + } + + self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + + let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + return panelHeightWithInset + } + + @objc func deleteButtonPressed() { + self.deleteMessages() + } + + @objc func forwardButtonPressed() { + self.shareMessages() + } + + @objc func shareButtonPressed() { + self.forwardMessages() + } +} diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift index 930c9b4bf7..dfd0e4ef98 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -37,13 +37,14 @@ public final class ListMessageItem: ListViewItem { let interaction: ListMessageItemInteraction let message: Message let selection: ChatHistoryMessageSelection + let hintIsLink: Bool let isGlobalSearchResult: Bool let header: ListViewItemHeader? public let selectable: Bool = true - public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, isGlobalSearchResult: Bool = false) { + public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false) { self.presentationData = presentationData self.context = context self.chatLocation = chatLocation @@ -57,16 +58,19 @@ public final class ListMessageItem: ListViewItem { self.header = nil } self.selection = selection + self.hintIsLink = hintIsLink self.isGlobalSearchResult = isGlobalSearchResult } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { var viewClassName: AnyClass = ListMessageSnippetItemNode.self - for media in self.message.media { - if let _ = media as? TelegramMediaFile { - viewClassName = ListMessageFileItemNode.self - break + if !self.hintIsLink { + for media in self.message.media { + if let _ = media as? TelegramMediaFile { + viewClassName = ListMessageFileItemNode.self + break + } } } diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 2c16dde5fa..3c552e190e 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -128,6 +128,12 @@ private class SearchBarTextField: UITextField { self.setNeedsLayout() } } + var selectedTokenIndex: Int? { + didSet { + self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) + self.setNeedsLayout() + } + } var theme: SearchBarNodeTheme diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index d3b258eaa4..c4b9484c1e 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -354,7 +354,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio controlColor: UIColor(rgb: 0x7e8791), accentTextColor: UIColor(rgb: 0x007ee5), backgroundColor: UIColor(rgb: 0xf7f7f7), - separatorColor: UIColor(rgb: 0xb1b1b1), + separatorColor: UIColor(rgb: 0xc8c7cc), badgeBackgroundColor: UIColor(rgb: 0xff3b30), badgeStrokeColor: UIColor(rgb: 0xff3b30), badgeTextColor: UIColor(rgb: 0xffffff), @@ -374,7 +374,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio inputPlaceholderTextColor: UIColor(rgb: 0x8e8e93), inputIconColor: UIColor(rgb: 0x8e8e93), inputClearButtonColor: UIColor(rgb: 0x7b7b81), - separatorColor: UIColor(rgb: 0xb1b1b1) + separatorColor: UIColor(rgb: 0xc8c7cc) ) let rootController = PresentationThemeRootController( @@ -685,7 +685,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let historyNavigation = PresentationThemeChatHistoryNavigation( fillColor: UIColor(rgb: 0xf7f7f7), - strokeColor: UIColor(rgb: 0xb1b1b1), + strokeColor: UIColor(rgb: 0xc8c7cc), foregroundColor: UIColor(rgb: 0x88888d), badgeBackgroundColor: UIColor(rgb: 0x007ee5), badgeStrokeColor: UIColor(rgb: 0x007ee5), @@ -751,7 +751,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio backgroundColor: UIColor(rgb: 0xffffff), primaryTextColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0x7e8791), - separatorColor: UIColor(rgb: 0xb1b1b1) + separatorColor: UIColor(rgb: 0xc8c7cc) ) ) ) diff --git a/submodules/TelegramUI/BUCK b/submodules/TelegramUI/BUCK index 758ae717c9..3ecb8c9add 100644 --- a/submodules/TelegramUI/BUCK +++ b/submodules/TelegramUI/BUCK @@ -210,6 +210,7 @@ framework( "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", "//submodules/GalleryData:GalleryData", + "//submodules/ChatInterfaceState:ChatInterfaceState", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index c2608f237b..5c56e9e3df 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -205,6 +205,7 @@ swift_library( "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", "//submodules/GalleryData:GalleryData", + "//submodules/ChatInterfaceState:ChatInterfaceState", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Sources/AudioWaveformNode.swift b/submodules/TelegramUI/Sources/AudioWaveformNode.swift index 1d490215c5..691bab2228 100644 --- a/submodules/TelegramUI/Sources/AudioWaveformNode.swift +++ b/submodules/TelegramUI/Sources/AudioWaveformNode.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit private final class AudioWaveformNodeParameters: NSObject { - let waveform: AudioWaveform? let color: UIColor? let gravity: AudioWaveformNode.Gravity? @@ -21,9 +20,7 @@ private final class AudioWaveformNodeParameters: NSObject { } final class AudioWaveformNode: ASDisplayNode { - enum Gravity { - case bottom case center } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b2a4cec338..cd4623f2b0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -62,6 +62,7 @@ import TooltipUI import StatisticsUI import MediaResources import GalleryData +import ChatInterfaceState extension ChatLocation { var peerId: PeerId { diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index 24780e77cd..e63cced173 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -10,6 +10,7 @@ import AccountContext import TextSelectionNode import ReactionSelectionNode import ContextUI +import ChatInterfaceState struct ChatInterfaceHighlightedState: Equatable { let messageStableId: UInt32 diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 92d5c7141e..0a39157275 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -13,6 +13,7 @@ import AccountContext import TelegramNotices import ReactionSelectionNode import TelegramUniversalVideoContent +import ChatInterfaceState final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 43d031ecf3..de6a6f2b35 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -16,6 +16,7 @@ import Emoji import AppBundle import ListMessageItem import AccountContext +import ChatInterfaceState private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { private let selectionGestureActivationThreshold: CGFloat = 5.0 @@ -76,7 +77,7 @@ public enum ChatHistoryListDisplayHeaders { public enum ChatHistoryListMode: Equatable { case bubbles - case list(search: Bool, reversed: Bool, displayHeaders: ChatHistoryListDisplayHeaders, isGlobalSearch: Bool) + case list(search: Bool, reversed: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool) } enum ChatHistoryViewScrollPosition { @@ -249,7 +250,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -259,8 +260,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -268,7 +268,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) - case let .list(_, _, _, _): + case let .list(_, _, _, _, _): assertionFailure() item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } @@ -293,7 +293,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -303,7 +303,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -311,7 +311,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) - case let .list(_, _, _, _): + case let .list(_, _, _, _, _): assertionFailure() item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } @@ -762,7 +762,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var reverse = false var includeSearchEntry = false - if case let .list(search, reverseValue, _, _) = mode { + if case let .list(search, reverseValue, _, _, _) = mode { includeSearchEntry = search reverse = reverseValue } @@ -1738,7 +1738,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { switch self.mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -1748,7 +1748,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index c96e11f581..c2f14590a5 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -6,6 +6,7 @@ import SyncCore import SwiftSignalKit import Display import AccountContext +import ChatInterfaceState func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index e219fad30a..1169d36b0e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -6,6 +6,7 @@ import Postbox import Display import AccountContext import Emoji +import ChatInterfaceState struct PossibleContextQueryTypes: OptionSet { var rawValue: Int32 diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 5b5c7e80ce..076c0acc6f 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -17,6 +17,7 @@ import ContextUI import GalleryUI import OverlayStatusController import PresentationDataUtils +import ChatInterfaceState struct PeerSpecificPackData { let peer: Peer diff --git a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift index 5f66288675..1be0b8f25f 100644 --- a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift @@ -6,6 +6,7 @@ import SyncCore import TelegramPresentationData import TelegramUIPreferences import AccountContext +import ChatInterfaceState enum ChatPresentationInputQueryKind: Int32 { case emoji diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index fa8a5881a7..24283d889e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -8,15 +8,11 @@ import SwiftSignalKit import TelegramPresentationData import LegacyComponents import AccountContext +import ChatInterfaceState private let offsetThreshold: CGFloat = 10.0 private let dismissOffsetThreshold: CGFloat = 70.0 -enum ChatTextInputMediaRecordingButtonMode: Int32 { - case audio = 0 - case video = 1 -} - private func findTargetView(_ view: UIView, point: CGPoint) -> UIView? { if view.bounds.contains(point) && view.tag == 0x01f2bca { return view diff --git a/submodules/TelegramUI/Sources/DeclareEncodables.swift b/submodules/TelegramUI/Sources/DeclareEncodables.swift index 0a5daaae3d..c469aa47f1 100644 --- a/submodules/TelegramUI/Sources/DeclareEncodables.swift +++ b/submodules/TelegramUI/Sources/DeclareEncodables.swift @@ -11,6 +11,7 @@ import SettingsUI import WallpaperResources import MediaResources import LocationUI +import ChatInterfaceState private var telegramUIDeclaredEncodables: Void = { declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) }) diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index f03ef83375..fe62d3d07b 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -20,6 +20,7 @@ import LanguageLinkPreviewUI import SettingsUI import UrlHandling import ShareController +import ChatInterfaceState private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { if case .default = navigation { diff --git a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift index d5d571e8e4..bec0fd70e2 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift @@ -164,7 +164,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu let chatLocationContextHolder = Atomic(value: nil) - self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none, isGlobalSearch: isGlobalSearch)) + self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: isGlobalSearch)) super.init() @@ -495,7 +495,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } let chatLocationContextHolder = Atomic(value: nil) - let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, isGlobalSearch: self.isGlobalSearch)) + let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) historyNode.preloadPages = true historyNode.stackFromBottom = true historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 14780e7beb..8bc01d9a22 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -72,7 +72,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.selectedMessagesPromise.set(.single(self.selectedMessages)) let chatLocationContextHolder = Atomic(value: nil) - self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, isGlobalSearch: false)) + self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) self.listNode.defaultToSynchronousTransactionWhileScrolling = true if tagMask == .music { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 0001feb3ca..05c5c84fd9 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -51,6 +51,7 @@ import SaveToCameraRoll import PeerInfoUI import ListMessageItem import GalleryData +import ChatInterfaceState protocol PeerInfoScreenItem: class { var id: AnyHashable { get } diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift index c0dea9dd1a..5ef8ed2ef9 100644 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift @@ -1,6 +1,7 @@ import Foundation import Postbox import TelegramPresentationData +import ChatInterfaceState enum PeerMediaCollectionMode: Int32 { case photoOrVideo diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index 9c3afbb6e3..ecb2de7a00 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -8,6 +8,7 @@ import OpenInExternalAppUI import MusicAlbumArtResources import LocalMediaResources import LocationResources +import ChatInterfaceState public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in if interfaceState == nil {