diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 35c7b34853..d3babbae12 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -891,6 +891,8 @@ public protocol SharedAccountContext: AnyObject { func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) + func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) + func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 15c9950b57..bae75268de 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -517,30 +517,50 @@ public enum ChatControllerSubject: Equatable { case id(EngineMessage.Id) case timestamp(Int32) } - - public struct ReplyOptions: Equatable { - public var hasQuote: Bool - - public init(hasQuote: Bool) { - self.hasQuote = hasQuote - } - } public struct ForwardOptions: Equatable { public var hideNames: Bool public var hideCaptions: Bool - public var replyOptions: ReplyOptions? - - public init(hideNames: Bool, hideCaptions: Bool, replyOptions: ReplyOptions?) { + public init(hideNames: Bool, hideCaptions: Bool) { self.hideNames = hideNames self.hideCaptions = hideCaptions - self.replyOptions = replyOptions } } - public struct MessageOptionsInfo: Equatable { - public struct ReplyQuote: Equatable { + public struct LinkOptions: Equatable { + public var messageText: String + public var messageEntities: [MessageTextEntity] + public var replyMessageId: EngineMessage.Id? + public var replyQuote: String? + public var url: String + public var webpage: TelegramMediaWebpage + public var linkBelowText: Bool + public var largeMedia: Bool + + public init( + messageText: String, + messageEntities: [MessageTextEntity], + replyMessageId: EngineMessage.Id?, + replyQuote: String?, + url: String, + webpage: TelegramMediaWebpage, + linkBelowText: Bool, + largeMedia: Bool + ) { + self.messageText = messageText + self.messageEntities = messageEntities + self.replyMessageId = replyMessageId + self.replyQuote = replyQuote + self.url = url + self.webpage = webpage + self.linkBelowText = linkBelowText + self.largeMedia = largeMedia + } + } + + public enum MessageOptionsInfo: Equatable { + public struct Quote: Equatable { public let messageId: EngineMessage.Id public let text: String @@ -550,16 +570,61 @@ public enum ChatControllerSubject: Equatable { } } - public enum Kind: Equatable { - case forward - case reply(initialQuote: ReplyQuote?) + public struct SelectionState: Equatable { + public var quote: Quote? + + public init(quote: Quote?) { + self.quote = quote + } } - public let kind: Kind - - public init(kind: Kind) { - self.kind = kind + public struct Reply: Equatable { + public var quote: Quote? + public var selectionState: Promise + + public init(quote: Quote?, selectionState: Promise) { + self.quote = quote + self.selectionState = selectionState + } + + public static func ==(lhs: Reply, rhs: Reply) -> Bool { + if lhs.quote != rhs.quote { + return false + } + if lhs.selectionState !== rhs.selectionState { + return false + } + return true + } } + + public struct Forward: Equatable { + public var options: Signal + + public init(options: Signal) { + self.options = options + } + + public static func ==(lhs: Forward, rhs: Forward) -> Bool { + return true + } + } + + public struct Link: Equatable { + public var options: Signal + + public init(options: Signal) { + self.options = options + } + + public static func ==(lhs: Link, rhs: Link) -> Bool { + return true + } + } + + case reply(Reply) + case forward(Forward) + case link(Link) } public struct MessageHighlight: Equatable { @@ -573,7 +638,7 @@ public enum ChatControllerSubject: Equatable { case message(id: MessageSubject, highlight: MessageHighlight?, timecode: Double?) case scheduledMessages case pinnedMessages(id: EngineMessage.Id?) - case messageOptions(peerIds: [EnginePeer.Id], ids: [EngineMessage.Id], info: MessageOptionsInfo, options: Signal) + case messageOptions(peerIds: [EnginePeer.Id], ids: [EngineMessage.Id], info: MessageOptionsInfo) public static func ==(lhs: ChatControllerSubject, rhs: ChatControllerSubject) -> Bool { switch lhs { @@ -595,8 +660,8 @@ public enum ChatControllerSubject: Equatable { } else { return false } - case let .messageOptions(lhsPeerIds, lhsIds, lhsInfo, _): - if case let .messageOptions(rhsPeerIds, rhsIds, rhsInfo, _) = rhs, lhsPeerIds == rhsPeerIds, lhsIds == rhsIds, lhsInfo == rhsInfo { + case let .messageOptions(lhsPeerIds, lhsIds, lhsInfo): + if case let .messageOptions(rhsPeerIds, rhsIds, rhsInfo) = rhs, lhsPeerIds == rhsPeerIds, lhsIds == rhsIds, lhsInfo == rhsInfo { return true } else { return false @@ -704,6 +769,32 @@ public protocol PeerInfoScreen: ViewController { var peerId: PeerId { get } } +public extension Peer { + func canSetupAutoremoveTimeout(accountPeerId: EnginePeer.Id) -> Bool { + if let _ = self as? TelegramSecretChat { + return false + } else if let group = self as? TelegramGroup { + if case .creator = group.role { + return true + } else if case let .admin(rights, _) = group.role { + if rights.rights.contains(.canDeleteMessages) { + return true + } + } + } else if let user = self as? TelegramUser { + if user.id != accountPeerId && user.botInfo == nil { + return true + } + } else if let channel = self as? TelegramChannel { + if channel.hasPermission(.deleteAllMessages) { + return true + } + } + + return false + } +} + public protocol ChatController: ViewController { var chatLocation: ChatLocation { get } var canReadHistory: ValuePromise { get } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index d685fea9aa..3c87658c13 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -779,6 +779,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in + }, presentLinkOptions: { _ in }, shareSelectedMessages: { }, updateTextInputStateAndMode: { [weak self] f in if let strongSelf = self { diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 895c1b41e8..233e5db0fc 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -330,10 +330,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in - c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) - c.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil) + c.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true) }))) } } @@ -658,7 +658,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: }))) //c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - c.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil) + c.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true) }))) items.append(.separator) @@ -813,7 +813,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }))) - c.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil) + c.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) } }))) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 4d8b8c8ec6..d1f433827e 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -3603,7 +3603,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { return nil case .links: var media: [EngineMedia] = [] - media.append(.webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))))) + media.append(.webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil, displayOptions: .default))))) let message = EngineMessage( stableId: 0, stableVersion: 0, diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index eb3c73a555..689e5d8c06 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -81,6 +81,7 @@ public final class ChatPanelInterfaceInteraction { public let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void public let presentForwardOptions: (ASDisplayNode) -> Void public let presentReplyOptions: (ASDisplayNode) -> Void + public let presentLinkOptions: (ASDisplayNode) -> Void public let shareSelectedMessages: () -> Void public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void public let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void @@ -186,6 +187,7 @@ public final class ChatPanelInterfaceInteraction { updateForwardOptionsState: @escaping ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void, presentForwardOptions: @escaping (ASDisplayNode) -> Void, presentReplyOptions: @escaping (ASDisplayNode) -> Void, + presentLinkOptions: @escaping (ASDisplayNode) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, @@ -290,6 +292,7 @@ public final class ChatPanelInterfaceInteraction { self.updateForwardOptionsState = updateForwardOptionsState self.presentForwardOptions = presentForwardOptions self.presentReplyOptions = presentReplyOptions + self.presentLinkOptions = presentLinkOptions self.shareSelectedMessages = shareSelectedMessages self.updateTextInputStateAndMode = updateTextInputStateAndMode self.updateInputModeAndDismissedButtonKeyboardMessageId = updateInputModeAndDismissedButtonKeyboardMessageId @@ -402,6 +405,7 @@ public final class ChatPanelInterfaceInteraction { }, updateForwardOptionsState: { _ in }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in + }, presentLinkOptions: { _ in }, shareSelectedMessages: { }, updateTextInputStateAndMode: updateTextInputStateAndMode, updateInputModeAndDismissedButtonKeyboardMessageId: updateInputModeAndDismissedButtonKeyboardMessageId, openStickers: { }, editMessage: { diff --git a/submodules/ContextUI/BUILD b/submodules/ContextUI/BUILD index 6d9b1e33e8..2fa56628e2 100644 --- a/submodules/ContextUI/BUILD +++ b/submodules/ContextUI/BUILD @@ -25,6 +25,9 @@ swift_library( "//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard", "//submodules/UndoUI:UndoUI", "//submodules/AnimationUI:AnimationUI", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/TabSelectorComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index d6f624b30a..e232d788ae 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -24,7 +24,7 @@ public protocol ContextControllerProtocol: ViewController { func dismiss(completion: (() -> Void)?) func getActionsMinHeight() -> ContextController.ActionsHeight? - func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?) + func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?, animated: Bool) func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) func pushItems(items: Signal) func popItems() @@ -232,14 +232,19 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> return targetWindowFrame } -private final class ContextControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { +final class ContextControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { + private weak var controller: ContextController? private var presentationData: PresentationData - private let source: ContextContentSource - private var items: Signal - private let beginDismiss: (ContextMenuActionResult) -> Void + + private let configuration: ContextController.Configuration + + private let legacySource: ContextContentSource + private var legacyItems: Signal + + let beginDismiss: (ContextMenuActionResult) -> Void private let beganAnimatingOut: () -> Void private let attemptTransitionControllerIntoNavigation: () -> Void - fileprivate var dismissedForCancel: (() -> Void)? + var dismissedForCancel: (() -> Void)? private let getController: () -> ContextControllerProtocol? private weak var gesture: ContextGesture? @@ -260,8 +265,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi private let dismissNode: ASDisplayNode private let dismissAccessibilityArea: AccessibilityAreaNode - private var presentationNode: ContextControllerPresentationNode? - private var currentPresentationStateTransition: ContextControllerPresentationNodeStateTransition? + private var sourceContainer: ContextSourceContainer? private let clippingNode: ASDisplayNode private let scrollNode: ASScrollNode @@ -288,32 +292,33 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi private let blurBackground: Bool var overlayWantsToBeBelowKeyboard: Bool { - if let presentationNode = self.presentationNode { - return presentationNode.wantsDisplayBelowKeyboard() - } else { + guard let sourceContainer = self.sourceContainer else { return false } + return sourceContainer.overlayWantsToBeBelowKeyboard } init( controller: ContextController, presentationData: PresentationData, - source: ContextContentSource, - items: Signal, + configuration: ContextController.Configuration, beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void ) { + self.controller = controller self.presentationData = presentationData - self.source = source - self.items = items + self.configuration = configuration self.beginDismiss = beginDismiss self.beganAnimatingOut = beganAnimatingOut self.attemptTransitionControllerIntoNavigation = attemptTransitionControllerIntoNavigation self.gesture = gesture + self.legacySource = configuration.sources[0].source + self.legacyItems = configuration.sources[0].items + self.getController = { [weak controller] in return controller } @@ -359,10 +364,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi var updateLayout: (() -> Void)? var blurBackground = true - if case .reference = source { - blurBackground = false - } else if case let .extracted(extractedSource) = source, !extractedSource.blurBackground { - blurBackground = false + if let mainSource = configuration.sources.first(where: { $0.id == configuration.initialId }) { + if case .reference = mainSource.source { + blurBackground = false + } else if case let .extracted(extractedSource) = mainSource.source, !extractedSource.blurBackground { + blurBackground = false + } } self.blurBackground = blurBackground @@ -423,9 +430,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } if strongSelf.didMoveFromInitialGesturePoint { - if let presentationNode = strongSelf.presentationNode { - let presentationPoint = strongSelf.view.convert(localPoint, to: presentationNode.view) - presentationNode.highlightGestureMoved(location: presentationPoint, hover: false) + if let sourceContainer = strongSelf.sourceContainer { + let presentationPoint = strongSelf.view.convert(localPoint, to: sourceContainer.view) + sourceContainer.highlightGestureMoved(location: presentationPoint, hover: false) } else { let actionPoint = strongSelf.view.convert(localPoint, to: strongSelf.actionsContainerNode.view) let actionNode = strongSelf.actionsContainerNode.actionNode(at: actionPoint) @@ -447,8 +454,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } recognizer.externalUpdated = nil if strongSelf.didMoveFromInitialGesturePoint { - if let presentationNode = strongSelf.presentationNode { - presentationNode.highlightGestureFinished(performAction: viewAndPoint != nil) + if let sourceContainer = strongSelf.sourceContainer { + sourceContainer.highlightGestureFinished(performAction: viewAndPoint != nil) } else { if let (_, _) = viewAndPoint { if let highlightedActionNode = strongSelf.highlightedActionNode { @@ -485,9 +492,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } if strongSelf.didMoveFromInitialGesturePoint { - if let presentationNode = strongSelf.presentationNode { - let presentationPoint = strongSelf.view.convert(localPoint, to: presentationNode.view) - presentationNode.highlightGestureMoved(location: presentationPoint, hover: false) + if let sourceContainer = strongSelf.sourceContainer { + let presentationPoint = strongSelf.view.convert(localPoint, to: sourceContainer.view) + sourceContainer.highlightGestureMoved(location: presentationPoint, hover: false) } else { let actionPoint = strongSelf.view.convert(localPoint, to: strongSelf.actionsContainerNode.view) var actionNode = strongSelf.actionsContainerNode.actionNode(at: actionPoint) @@ -513,8 +520,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } gesture.externalUpdated = nil if strongSelf.didMoveFromInitialGesturePoint { - if let presentationNode = strongSelf.presentationNode { - presentationNode.highlightGestureFinished(performAction: viewAndPoint != nil) + if let sourceContainer = strongSelf.sourceContainer { + sourceContainer.highlightGestureFinished(performAction: viewAndPoint != nil) } else { if let (_, _) = viewAndPoint { if let highlightedActionNode = strongSelf.highlightedActionNode { @@ -532,22 +539,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } - switch source { - case .location, .reference, .extracted: - self.contentReady.set(.single(true)) - case let .controller(source): - self.contentReady.set(source.controller.ready.get()) - //TODO: - //self.contentReady.set(.single(true)) - } - self.initializeContent() - self.itemsDisposable.set((items - |> deliverOnMainQueue).start(next: { [weak self] items in - self?.setItems(items: items, minHeight: nil, previousActionsTransition: .scale) - })) - self.dismissAccessibilityArea.activate = { [weak self] in self?.dimNodeTapped() return true @@ -593,9 +586,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi switch gestureRecognizer.state { case .changed: - if let presentationNode = self.presentationNode { - let presentationPoint = self.view.convert(localPoint, to: presentationNode.view) - presentationNode.highlightGestureMoved(location: presentationPoint, hover: true) + if let sourceContainer = self.sourceContainer { + let presentationPoint = self.view.convert(localPoint, to: sourceContainer.view) + sourceContainer.highlightGestureMoved(location: presentationPoint, hover: true) } else { let actionPoint = self.view.convert(localPoint, to: self.actionsContainerNode.view) let actionNode = self.actionsContainerNode.actionNode(at: actionPoint) @@ -608,8 +601,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } case .ended, .cancelled: - if let presentationNode = self.presentationNode { - presentationNode.highlightGestureMoved(location: CGPoint(x: -1, y: -1), hover: true) + if let sourceContainer = self.sourceContainer { + sourceContainer.highlightGestureMoved(location: CGPoint(x: -1, y: -1), hover: true) } else { if let highlightedActionNode = self.highlightedActionNode { self.highlightedActionNode = nil @@ -622,230 +615,86 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } private func initializeContent() { - switch self.source { - case let .location(source): - let presentationNode = ContextControllerExtractedPresentationNode( - getController: { [weak self] in - return self?.getController() - }, - requestUpdate: { [weak self] transition in - guard let strongSelf = self else { - return + if self.configuration.sources.count == 1 { + switch self.configuration.sources[0].source { + case .location: + break + case let .reference(source): + if let controller = self.getController() as? ContextController, controller.workaroundUseLegacyImplementation { + self.contentReady.set(.single(true)) + + let transitionInfo = source.transitionInfo() + if let transitionInfo = transitionInfo { + let referenceView = transitionInfo.referenceView + self.contentContainerNode.contentNode = .reference(view: referenceView) + self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace + self.customPosition = transitionInfo.customPosition + var projectedFrame = convertFrame(referenceView.bounds, from: referenceView, to: self.view) + projectedFrame.origin.x += transitionInfo.insets.left + projectedFrame.size.width -= transitionInfo.insets.left + transitionInfo.insets.right + projectedFrame.origin.y += transitionInfo.insets.top + projectedFrame.size.width -= transitionInfo.insets.top + transitionInfo.insets.bottom + self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) } - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout( - layout: validLayout, - transition: transition, - previousActionsContainerNode: nil - ) - } - }, - requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let controller = strongSelf.getController() { - controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) - } - }, - requestDismiss: { [weak self] result in - guard let strongSelf = self else { - return - } - strongSelf.dismissedForCancel?() - strongSelf.beginDismiss(result) - }, - requestAnimateOut: { [weak self] result, completion in - guard let strongSelf = self else { - return - } - strongSelf.animateOut(result: result, completion: completion) - }, - source: .location(source) - ) - self.presentationNode = presentationNode - self.addSubnode(presentationNode) - case let .reference(source): - if let controller = self.getController() as? ContextController, controller.workaroundUseLegacyImplementation { - let transitionInfo = source.transitionInfo() - if let transitionInfo = transitionInfo { - let referenceView = transitionInfo.referenceView - self.contentContainerNode.contentNode = .reference(view: referenceView) - self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace - self.customPosition = transitionInfo.customPosition - var projectedFrame = convertFrame(referenceView.bounds, from: referenceView, to: self.view) - projectedFrame.origin.x += transitionInfo.insets.left - projectedFrame.size.width -= transitionInfo.insets.left + transitionInfo.insets.right - projectedFrame.origin.y += transitionInfo.insets.top - projectedFrame.size.width -= transitionInfo.insets.top + transitionInfo.insets.bottom - self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) - } - } else { - let presentationNode = ContextControllerExtractedPresentationNode( - getController: { [weak self] in - return self?.getController() - }, - requestUpdate: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout( - layout: validLayout, - transition: transition, - previousActionsContainerNode: nil - ) - } - }, - requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let controller = strongSelf.getController() { - controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) - } - }, - requestDismiss: { [weak self] result in - guard let strongSelf = self else { - return - } - strongSelf.dismissedForCancel?() - strongSelf.beginDismiss(result) - }, - requestAnimateOut: { [weak self] result, completion in - guard let strongSelf = self else { - return - } - strongSelf.animateOut(result: result, completion: completion) - }, - source: .reference(source) - ) - self.presentationNode = presentationNode - self.addSubnode(presentationNode) - } - case let .extracted(source): - let presentationNode = ContextControllerExtractedPresentationNode( - getController: { [weak self] in - return self?.getController() - }, - requestUpdate: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout( - layout: validLayout, - transition: transition, - previousActionsContainerNode: nil - ) - } - }, - requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let controller = strongSelf.getController() { - controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) - } - }, - requestDismiss: { [weak self] result in - guard let strongSelf = self else { - return - } - strongSelf.dismissedForCancel?() - strongSelf.beginDismiss(result) - }, - requestAnimateOut: { [weak self] result, completion in - guard let strongSelf = self else { - return - } - strongSelf.animateOut(result: result, completion: completion) - }, - source: .extracted(source) - ) - self.presentationNode = presentationNode - self.addSubnode(presentationNode) - case let .controller(source): - if "".isEmpty { - let transitionInfo = source.transitionInfo() - if let transitionInfo = transitionInfo, let (sourceView, sourceNodeRect) = transitionInfo.sourceNode() { - let contentParentNode = ContextControllerContentNode(sourceView: sourceView, controller: source.controller, tapped: { [weak self] in - self?.attemptTransitionControllerIntoNavigation() - }) - self.contentContainerNode.contentNode = .controller(contentParentNode) - self.scrollNode.addSubnode(self.contentContainerNode) - self.contentContainerNode.clipsToBounds = true - self.contentContainerNode.cornerRadius = 14.0 - self.contentContainerNode.addSubnode(contentParentNode) + self.itemsDisposable.set((self.configuration.sources[0].items + |> deliverOnMainQueue).start(next: { [weak self] items in + self?.setItems(items: items, minHeight: nil, previousActionsTransition: .scale) + })) - let projectedFrame = convertFrame(sourceNodeRect, from: sourceView, to: self.view) - self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) + return + } + case .extracted: + break + case let .controller(source): + if let controller = self.getController() as? ContextController, controller.workaroundUseLegacyImplementation { + self.contentReady.set(source.controller.ready.get()) + + let transitionInfo = source.transitionInfo() + if let transitionInfo = transitionInfo, let (sourceView, sourceNodeRect) = transitionInfo.sourceNode() { + let contentParentNode = ContextControllerContentNode(sourceView: sourceView, controller: source.controller, tapped: { [weak self] in + self?.attemptTransitionControllerIntoNavigation() + }) + self.contentContainerNode.contentNode = .controller(contentParentNode) + self.scrollNode.addSubnode(self.contentContainerNode) + self.contentContainerNode.clipsToBounds = true + self.contentContainerNode.cornerRadius = 14.0 + self.contentContainerNode.addSubnode(contentParentNode) + + let projectedFrame = convertFrame(sourceNodeRect, from: sourceView, to: self.view) + self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame) + } + + self.itemsDisposable.set((self.configuration.sources[0].items + |> deliverOnMainQueue).start(next: { [weak self] items in + self?.setItems(items: items, minHeight: nil, previousActionsTransition: .scale) + })) + + return } - } else { - let presentationNode = ContextControllerExtractedPresentationNode( - getController: { [weak self] in - return self?.getController() - }, - requestUpdate: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout( - layout: validLayout, - transition: transition, - previousActionsContainerNode: nil - ) - } - }, - requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in - guard let strongSelf = self else { - return - } - if let controller = strongSelf.getController() { - controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) - } - }, - requestDismiss: { [weak self] result in - guard let strongSelf = self else { - return - } - strongSelf.dismissedForCancel?() - strongSelf.beginDismiss(result) - }, - requestAnimateOut: { [weak self] result, completion in - guard let strongSelf = self else { - return - } - strongSelf.animateOut(result: result, completion: completion) - }, - source: .controller(source) - ) - self.presentationNode = presentationNode - self.addSubnode(presentationNode) } } + + if let controller = self.controller { + let sourceContainer = ContextSourceContainer(controller: controller, configuration: self.configuration) + self.contentReady.set(sourceContainer.ready.get()) + self.itemsReady.set(.single(true)) + self.sourceContainer = sourceContainer + self.addSubnode(sourceContainer) + } } func animateIn() { self.gesture?.endPressedAppearance() self.hapticFeedback.impact() - if let _ = self.presentationNode { + if let sourceContainer = self.sourceContainer { self.didCompleteAnimationIn = true - self.currentPresentationStateTransition = .animateIn - if let validLayout = self.validLayout { - self.updateLayout( - layout: validLayout, - transition: .animated(duration: 0.5, curve: .spring), - previousActionsContainerNode: nil - ) - } + sourceContainer.animateIn() return } - switch self.source { + switch self.legacySource { case .location, .reference: break case .extracted: @@ -935,7 +784,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi case let .extracted(extracted, keepInPlace): let springDuration: Double = 0.42 * animationDurationFactor var springDamping: CGFloat = 104.0 - if case let .extracted(source) = self.source, source.centerVertically { + if case let .extracted(source) = self.legacySource, source.centerVertically { springDamping = 124.0 } @@ -949,7 +798,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi var actionsDuration = springDuration var actionsOffset: CGFloat = 0.0 var contentDuration = springDuration - if case let .extracted(source) = self.source, source.centerVertically { + if case let .extracted(source) = self.legacySource, source.centerVertically { actionsOffset = -(originalProjectedContentViewFrame.1.height - originalProjectedContentViewFrame.0.height) * 0.57 actionsDuration *= 1.0 contentDuration *= 0.9 @@ -988,7 +837,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.contentContainerNode.layer.animateSpring(from: min(localSourceFrame.width / self.contentContainerNode.frame.width, localSourceFrame.height / self.contentContainerNode.frame.height) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) - switch self.source { + switch self.legacySource { case let .controller(controller): controller.animatedIn() default: @@ -1024,28 +873,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.beganAnimatingOut() - if let _ = self.presentationNode { - self.currentPresentationStateTransition = .animateOut(result: initialResult, completion: completion) - if let validLayout = self.validLayout { - if case let .custom(transition) = initialResult { - self.delayLayoutUpdate = true - Queue.mainQueue().after(0.1) { - self.delayLayoutUpdate = false - self.updateLayout( - layout: validLayout, - transition: transition, - previousActionsContainerNode: nil - ) - self.isAnimatingOut = true - } - } else { - self.updateLayout( - layout: validLayout, - transition: .animated(duration: 0.35, curve: .easeInOut), - previousActionsContainerNode: nil - ) - } - } + if let sourceContainer = self.sourceContainer { + sourceContainer.animateOut(result: initialResult, completion: completion) return } @@ -1054,7 +883,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi var result = initialResult - switch self.source { + switch self.legacySource { case let .location(source): let transitionInfo = source.transitionInfo() if transitionInfo == nil { @@ -1296,7 +1125,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } var actionsOffset: CGFloat = 0.0 - if case let .extracted(source) = self.source, source.centerVertically { + if case let .extracted(source) = self.legacySource, source.centerVertically { actionsOffset = -localSourceFrame.width * 0.6 } @@ -1469,24 +1298,23 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { - if let presentationNode = self.presentationNode { - presentationNode.addRelativeContentOffset(offset, transition: transition) + if let sourceContainer = self.sourceContainer { + sourceContainer.addRelativeContentOffset(offset, transition: transition) } } func cancelReactionAnimation() { - if let presentationNode = self.presentationNode { - presentationNode.cancelReactionAnimation() + if let sourceContainer = self.sourceContainer { + sourceContainer.cancelReactionAnimation() } } func animateOutToReaction(value: MessageReaction.Reaction, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, reducedCurve: Bool, completion: @escaping () -> Void) { - if let presentationNode = self.presentationNode { - presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, reducedCurve: reducedCurve, completion: completion) + if let sourceContainer = self.sourceContainer { + sourceContainer.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, reducedCurve: reducedCurve, completion: completion) } } - func getActionsMinHeight() -> ContextController.ActionsHeight? { if !self.actionsContainerNode.bounds.height.isZero { return ContextController.ActionsHeight( @@ -1498,21 +1326,25 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } - func setItemsSignal(items: Signal, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) { - self.items = items - self.itemsDisposable.set((items - |> deliverOnMainQueue).start(next: { [weak self] items in - guard let strongSelf = self else { - return - } - strongSelf.setItems(items: items, minHeight: minHeight, previousActionsTransition: previousActionsTransition) - })) + func setItemsSignal(items: Signal, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition, animated: Bool) { + if let sourceContainer = self.sourceContainer { + sourceContainer.setItems(items: items, animated: animated) + } else { + self.legacyItems = items + self.itemsDisposable.set((items + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let strongSelf = self else { + return + } + strongSelf.setItems(items: items, minHeight: minHeight, previousActionsTransition: previousActionsTransition) + })) + } } private func setItems(items: ContextController.Items, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) { - if let presentationNode = self.presentationNode { + if let sourceContainer = self.sourceContainer { let disableAnimations = self.getController()?.immediateItemsTransitionAnimation == true - presentationNode.replaceItems(items: items, animated: self.didCompleteAnimationIn && !disableAnimations) + sourceContainer.setItems(items: .single(items), animated: !disableAnimations) if !self.didSetItemsReady { self.didSetItemsReady = true @@ -1554,18 +1386,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } func pushItems(items: Signal) { - self.itemsDisposable.set((items - |> deliverOnMainQueue).start(next: { [weak self] items in - guard let strongSelf = self, let presentationNode = strongSelf.presentationNode else { - return - } - presentationNode.pushItems(items: items) - })) + if let sourceContainer = self.sourceContainer { + sourceContainer.pushItems(items: items) + } } func popItems() { - if let presentationNode = self.presentationNode { - presentationNode.popItems() + if let sourceContainer = self.sourceContainer { + sourceContainer.popItems() } } @@ -1593,16 +1421,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.validLayout = layout - let presentationStateTransition = self.currentPresentationStateTransition - self.currentPresentationStateTransition = .none - - if let presentationNode = self.presentationNode { - transition.updateFrame(node: presentationNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - presentationNode.update( + if let sourceContainer = self.sourceContainer { + transition.updateFrame(node: sourceContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) + sourceContainer.update( presentationData: self.presentationData, layout: layout, - transition: transition, - stateTransition: presentationStateTransition + transition: transition ) return } @@ -1618,8 +1442,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi switch layout.metrics.widthClass { case .compact: - if case .reference = self.source { - } else if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground { + if case .reference = self.legacySource { + } else if case let .extracted(extractedSource) = self.legacySource, !extractedSource.blurBackground { } else if self.effectView.superview == nil { self.view.insertSubview(self.effectView, at: 0) if #available(iOS 10.0, *) { @@ -1634,8 +1458,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.dimNode.isHidden = false self.withoutBlurDimNode.isHidden = true case .regular: - if case .reference = self.source { - } else if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground { + if case .reference = self.legacySource { + } else if case let .extracted(extractedSource) = self.legacySource, !extractedSource.blurBackground { } else if self.effectView.superview != nil { self.effectView.removeFromSuperview() self.withoutBlurDimNode.alpha = 1.0 @@ -1744,7 +1568,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } case let .extracted(contentParentNode, keepInPlace): var centerVertically = false - if case let .extracted(source) = self.source, source.centerVertically { + if case let .extracted(source) = self.legacySource, source.centerVertically { centerVertically = true } let contentActionsSpacing: CGFloat = keepInPlace ? 16.0 : 8.0 @@ -1896,7 +1720,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } case let .controller(contentParentNode): var projectedFrame: CGRect = convertFrame(contentParentNode.sourceView.bounds, from: contentParentNode.sourceView, to: self.view) - switch self.source { + switch self.legacySource { case let .controller(source): let transitionInfo = source.transitionInfo() if let (sourceView, sourceRect) = transitionInfo?.sourceNode() { @@ -2127,8 +1951,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } - if let presentationNode = self.presentationNode { - return presentationNode.hitTest(self.view.convert(point, to: presentationNode.view), with: event) + if let sourceContainer = self.sourceContainer { + return sourceContainer.hitTest(self.view.convert(point, to: sourceContainer.view), with: event) } let mappedPoint = self.view.convert(point, to: self.scrollNode.view) @@ -2140,7 +1964,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi maybePassthrough = passthroughTouchEvent(self.view, point) } case let .extracted(contentParentNode, _): - if case let .extracted(source) = self.source { + if case let .extracted(source) = self.legacySource { if !source.ignoreContentTouches { let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view) if let result = contentParentNode.contentNode.customHitTest?(contentPoint) { @@ -2156,7 +1980,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } case let .controller(controller): var passthrough = false - switch self.source { + switch self.legacySource { case let .controller(controllerSource): passthrough = controllerSource.passthroughTouches default: @@ -2165,9 +1989,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if passthrough { let controllerPoint = self.view.convert(point, to: controller.controller.view) if let result = controller.controller.view.hitTest(controllerPoint, with: event) { - #if DEBUG - //return controller.view - #endif return result } } @@ -2198,15 +2019,15 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } fileprivate func performHighlightedAction() { - self.presentationNode?.highlightGestureFinished(performAction: true) + self.sourceContainer?.performHighlightedAction() } fileprivate func decreaseHighlightedIndex() { - self.presentationNode?.decreaseHighlightedIndex() + self.sourceContainer?.decreaseHighlightedIndex() } fileprivate func increaseHighlightedIndex() { - self.presentationNode?.increaseHighlightedIndex() + self.sourceContainer?.increaseHighlightedIndex() } } @@ -2374,6 +2195,30 @@ public protocol ContextControllerItemsContent: AnyObject { } public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol, KeyShortcutResponder { + public final class Source { + public let id: AnyHashable + public let title: String + public let source: ContextContentSource + public let items: Signal + + public init(id: AnyHashable, title: String, source: ContextContentSource, items: Signal) { + self.id = id + self.title = title + self.source = source + self.items = items + } + } + + public final class Configuration { + public let sources: [Source] + public let initialId: AnyHashable + + public init(sources: [Source], initialId: AnyHashable) { + self.sources = sources + self.initialId = initialId + } + } + public struct Items { public enum Content { case list([ContextMenuItem]) @@ -2390,8 +2235,20 @@ public final class ContextController: ViewController, StandalonePresentableContr public var disablePositionLock: Bool public var tip: Tip? public var tipSignal: Signal? + public var dismissed: (() -> Void)? - public init(content: Content, context: AccountContext? = nil, reactionItems: [ReactionContextItem] = [], selectedReactionItems: Set = Set(), animationCache: AnimationCache? = nil, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)? = nil, disablePositionLock: Bool = false, tip: Tip? = nil, tipSignal: Signal? = nil) { + public init( + content: Content, + context: AccountContext? = nil, + reactionItems: [ReactionContextItem] = [], + selectedReactionItems: Set = Set(), + animationCache: AnimationCache? = nil, + getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)? = nil, + disablePositionLock: Bool = false, + tip: Tip? = nil, + tipSignal: Signal? = nil, + dismissed: (() -> Void)? = nil + ) { self.content = content self.context = context self.animationCache = animationCache @@ -2401,6 +2258,7 @@ public final class ContextController: ViewController, StandalonePresentableContr self.disablePositionLock = disablePositionLock self.tip = tip self.tipSignal = tipSignal + self.dismissed = dismissed } public init() { @@ -2412,6 +2270,7 @@ public final class ContextController: ViewController, StandalonePresentableContr self.disablePositionLock = false self.tip = nil self.tipSignal = nil + self.dismissed = nil } } @@ -2487,8 +2346,7 @@ public final class ContextController: ViewController, StandalonePresentableContr } private var presentationData: PresentationData - private let source: ContextContentSource - private var items: Signal + private let configuration: ContextController.Configuration private let _ready = Promise() override public var ready: Promise { @@ -2511,7 +2369,7 @@ public final class ContextController: ViewController, StandalonePresentableContr } } - private var controllerNode: ContextControllerNode { + var controllerNode: ContextControllerNode { return self.displayNode as! ContextControllerNode } @@ -2540,17 +2398,41 @@ public final class ContextController: ViewController, StandalonePresentableContr public var getOverlayViews: (() -> [UIView])? - public init(presentationData: PresentationData, source: ContextContentSource, items: Signal, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, workaroundUseLegacyImplementation: Bool = false) { + convenience public init(presentationData: PresentationData, source: ContextContentSource, items: Signal, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, workaroundUseLegacyImplementation: Bool = false) { + self.init( + presentationData: presentationData, + configuration: ContextController.Configuration( + sources: [ContextController.Source( + id: AnyHashable(0 as Int), + title: "", + source: source, + items: items + )], + initialId: AnyHashable(0 as Int) + ), + recognizer: recognizer, + gesture: gesture, + workaroundUseLegacyImplementation: workaroundUseLegacyImplementation + ) + } + + public init( + presentationData: PresentationData, + configuration: ContextController.Configuration, + recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, + gesture: ContextGesture? = nil, + workaroundUseLegacyImplementation: Bool = false + ) { self.presentationData = presentationData - self.source = source - self.items = items + self.configuration = configuration self.recognizer = recognizer self.gesture = gesture self.workaroundUseLegacyImplementation = workaroundUseLegacyImplementation super.init(navigationBarPresentationData: nil) - - switch source { + + if let mainSource = configuration.sources.first(where: { $0.id == configuration.initialId }) { + switch mainSource.source { case let .location(locationSource): self.statusBar.statusBarStyle = .Ignore @@ -2592,6 +2474,7 @@ public final class ContextController: ViewController, StandalonePresentableContr }).strict() case .controller: self.statusBar.statusBarStyle = .Hide + } } self.lockOrientation = true @@ -2607,7 +2490,7 @@ public final class ContextController: ViewController, StandalonePresentableContr } override public func loadDisplayNode() { - self.displayNode = ContextControllerNode(controller: self, presentationData: self.presentationData, source: self.source, items: self.items, beginDismiss: { [weak self] result in + self.displayNode = ContextControllerNode(controller: self, presentationData: self.presentationData, configuration: self.configuration, beginDismiss: { [weak self] result in self?.dismiss(result: result, completion: nil) }, recognizer: self.recognizer, gesture: self.gesture, beganAnimatingOut: { [weak self] in guard let strongSelf = self else { @@ -2622,19 +2505,7 @@ public final class ContextController: ViewController, StandalonePresentableContr } return true } - }, attemptTransitionControllerIntoNavigation: { [weak self] in - guard let strongSelf = self else { - return - } - switch strongSelf.source { - /*case let .controller(controller): - if let navigationController = controller.navigationController { - strongSelf.presentingViewController?.dismiss(animated: false, completion: nil) - navigationController.pushViewController(controller.controller, animated: false) - }*/ - default: - break - } + }, attemptTransitionControllerIntoNavigation: { }) self.controllerNode.dismissedForCancel = self.dismissedForCancel self.displayNodeDidLoad() @@ -2684,18 +2555,24 @@ public final class ContextController: ViewController, StandalonePresentableContr return nil } - public func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?) { - self.items = items + public func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?, animated: Bool) { + //self.items = items + if self.isNodeLoaded { self.immediateItemsTransitionAnimation = false - self.controllerNode.setItemsSignal(items: items, minHeight: minHeight, previousActionsTransition: .scale) + self.controllerNode.setItemsSignal(items: items, minHeight: minHeight, previousActionsTransition: .scale, animated: animated) + } else { + assertionFailure() } } public func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) { - self.items = items + //self.items = items + if self.isNodeLoaded { - self.controllerNode.setItemsSignal(items: items, minHeight: minHeight, previousActionsTransition: previousActionsTransition) + self.controllerNode.setItemsSignal(items: items, minHeight: minHeight, previousActionsTransition: previousActionsTransition, animated: true) + } else { + assertionFailure() } } @@ -2742,6 +2619,10 @@ public final class ContextController: ViewController, StandalonePresentableContr self.dismiss(result: .default, completion: completion) } + public func dismissWithCustomTransition(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) { + self.dismiss(result: .custom(transition), completion: nil) + } + public func dismissWithoutContent() { self.dismiss(result: .dismissWithoutContent, completion: nil) } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 8491ae7bc8..9e2c34deb7 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -44,6 +44,7 @@ public protocol ContextControllerActionsStackItem: AnyObject { var tip: ContextController.Tip? { get } var tipSignal: Signal? { get } var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)? { get } + var dismissed: (() -> Void)? { get } } protocol ContextControllerActionsListItemNode: ASDisplayNode { @@ -836,17 +837,20 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)? let tip: ContextController.Tip? let tipSignal: Signal? + let dismissed: (() -> Void)? init( items: [ContextMenuItem], reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)?, tip: ContextController.Tip?, - tipSignal: Signal? + tipSignal: Signal?, + dismissed: (() -> Void)? ) { self.items = items self.reactionItems = reactionItems self.tip = tip self.tipSignal = tipSignal + self.dismissed = dismissed } func node( @@ -928,17 +932,20 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)? let tip: ContextController.Tip? let tipSignal: Signal? + let dismissed: (() -> Void)? init( content: ContextControllerItemsContent, reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)?, tip: ContextController.Tip?, - tipSignal: Signal? + tipSignal: Signal?, + dismissed: (() -> Void)? ) { self.content = content self.reactionItems = reactionItems self.tip = tip self.tipSignal = tipSignal + self.dismissed = dismissed } func node( @@ -963,11 +970,11 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C } switch items.content { case let .list(listItems): - return [ContextControllerActionsListStackItem(items: listItems, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal)] + return [ContextControllerActionsListStackItem(items: listItems, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)] case let .twoLists(listItems1, listItems2): - return [ContextControllerActionsListStackItem(items: listItems1, reactionItems: nil, tip: nil, tipSignal: nil), ContextControllerActionsListStackItem(items: listItems2, reactionItems: nil, tip: nil, tipSignal: nil)] + return [ContextControllerActionsListStackItem(items: listItems1, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: items.dismissed), ContextControllerActionsListStackItem(items: listItems2, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: nil)] case let .custom(customContent): - return [ContextControllerActionsCustomStackItem(content: customContent, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal)] + return [ContextControllerActionsCustomStackItem(content: customContent, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)] } } @@ -1083,6 +1090,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { let tipSignal: Signal? var tipNode: InnerTextSelectionTipContainerNode? let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)? + let itemDismissed: (() -> Void)? var storedScrollingState: CGFloat? let positionLock: CGFloat? @@ -1097,6 +1105,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { tip: ContextController.Tip?, tipSignal: Signal?, reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?)?, + itemDismissed: (() -> Void)?, positionLock: CGFloat? ) { self.getController = getController @@ -1113,6 +1122,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { self.dimNode.alpha = 0.0 self.reactionItems = reactionItems + self.itemDismissed = itemDismissed self.positionLock = positionLock self.tip = tip @@ -1339,6 +1349,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { tip: item.tip, tipSignal: item.tipSignal, reactionItems: item.reactionItems, + itemDismissed: item.dismissed, positionLock: positionLock ) self.itemContainers.append(itemContainer) @@ -1365,6 +1376,8 @@ final class ContextControllerActionsStackNode: ASDisplayNode { let itemContainer = self.itemContainers[self.itemContainers.count - 1] self.itemContainers.remove(at: self.itemContainers.count - 1) self.dismissingItemContainers.append((itemContainer, true)) + + itemContainer.itemDismissed?() } self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1 diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 305f5cfc39..743df9193d 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -163,22 +163,50 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo private final class ControllerContentNode: ASDisplayNode { let controller: ViewController + let passthroughTouches: Bool + var storedContentHeight: CGFloat? - init(controller: ViewController) { + init(controller: ViewController, passthroughTouches: Bool) { self.controller = controller + self.passthroughTouches = passthroughTouches super.init() + self.clipsToBounds = true + self.cornerRadius = 14.0 + self.addSubnode(self.controller.displayNode) } - func update(presentationData: PresentationData, size: CGSize, transition: ContainedViewLayoutTransition) { + func update(presentationData: PresentationData, parentLayout: ContainerViewLayout, size: CGSize, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.controller.displayNode, frame: CGRect(origin: CGPoint(), size: size)) + self.controller.containerLayoutUpdated( + ContainerViewLayout( + size: size, + metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), + deviceMetrics: parentLayout.deviceMetrics, + intrinsicInsets: UIEdgeInsets(), + safeInsets: UIEdgeInsets(), + additionalInsets: UIEdgeInsets(), + statusBarHeight: nil, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ), + transition: transition + ) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil } + if self.passthroughTouches { + let controllerPoint = self.view.convert(point, to: self.controller.view) + if let result = self.controller.view.hitTest(controllerPoint, with: event) { + return result + } + } return self.view } } @@ -206,7 +234,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo private let requestAnimateOut: (ContextMenuActionResult, @escaping () -> Void) -> Void private let source: ContentSource - private let backgroundNode: NavigationBackgroundNode private let dismissTapNode: ASDisplayNode private let dismissAccessibilityArea: AccessibilityAreaNode private let clippingNode: ASDisplayNode @@ -254,8 +281,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.requestAnimateOut = requestAnimateOut self.source = source - self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: false) - self.dismissTapNode = ASDisplayNode() self.dismissAccessibilityArea = AccessibilityAreaNode() @@ -302,7 +327,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.view.addSubview(self.scroller) self.scroller.isHidden = true - self.addSubnode(self.backgroundNode) self.addSubnode(self.clippingNode) self.clippingNode.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.dismissTapNode) @@ -476,11 +500,21 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if !items.disablePositionLock { positionLock = self.getActionsStackPositionLock() } + if self.actionsStackNode.topPositionLock == nil { + if let contentNode = self.controllerContentNode, contentNode.bounds.height != 0.0 { + contentNode.storedContentHeight = contentNode.bounds.height + } + } self.actionsStackNode.push(item: makeContextControllerActionsStackItem(items: items).first!, currentScrollingState: currentScrollingState, positionLock: positionLock, animated: true) } func popItems() { self.actionsStackNode.pop() + if self.actionsStackNode.topPositionLock == nil { + if let contentNode = self.controllerContentNode { + contentNode.storedContentHeight = nil + } + } } private func getCurrentScrollingState() -> CGFloat { @@ -515,9 +549,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo ) { self.validLayout = layout - let contentActionsSpacing: CGFloat = 7.0 + var contentActionsSpacing: CGFloat = 7.0 let actionsEdgeInset: CGFloat - let actionsSideInset: CGFloat = 6.0 + let actionsSideInset: CGFloat let topInset: CGFloat = layout.insets(options: .statusBar).top + 8.0 let bottomInset: CGFloat = 10.0 @@ -533,26 +567,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo switch self.source { case .location, .reference: - self.backgroundNode.updateColor( - color: .clear, - enableBlur: false, - forceKeepBlur: false, - transition: .immediate - ) actionsEdgeInset = 16.0 - case .extracted, .controller: - self.backgroundNode.updateColor( - color: presentationData.theme.contextMenu.dimColor, - enableBlur: true, - forceKeepBlur: true, - transition: .immediate - ) + actionsSideInset = 6.0 + case .extracted: actionsEdgeInset = 12.0 + actionsSideInset = 6.0 + case .controller: + actionsEdgeInset = 12.0 + actionsSideInset = -2.0 + contentActionsSpacing += 3.0 } - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) - self.backgroundNode.update(size: layout.size, transition: transition) - transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) if self.scrollNode.frame != CGRect(origin: CGPoint(), size: layout.size) { transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) @@ -583,7 +608,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } else { switch self.source { case let .controller(source): - controllerContentNode = ControllerContentNode(controller: source.controller) + let controllerContentNodeValue = ControllerContentNode(controller: source.controller, passthroughTouches: source.passthroughTouches) + + //source.controller.viewWillAppear(false) + //source.controller.setIgnoreAppearanceMethodInvocations(true) + + self.scrollNode.insertSubnode(controllerContentNodeValue, aboveSubnode: self.actionsContainerNode) + self.controllerContentNode = controllerContentNodeValue + controllerContentNode = controllerContentNodeValue + contentTransition = .immediate + + //source.controller.setIgnoreAppearanceMethodInvocations(false) + //source.controller.viewDidAppear(false) case .location, .reference, .extracted: controllerContentNode = nil } @@ -688,6 +724,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let contentParentGlobalFrame: CGRect var contentRect: CGRect + var isContentResizeableVertically: Bool = false + let _ = isContentResizeableVertically switch self.source { case let .location(location): @@ -718,10 +756,21 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo return } case .controller: - //TODO if let contentNode = controllerContentNode { - let _ = contentNode - contentRect = CGRect(origin: CGPoint(x: layout.size.width * 0.5 - 100.0, y: layout.size.height * 0.5 - 100.0), size: CGSize(width: 200.0, height: 200.0)) + var defaultContentSize = CGSize(width: layout.size.width - 12.0 * 2.0, height: layout.size.height - 12.0 * 2.0 - contentTopInset - layout.safeInsets.bottom) + defaultContentSize.height = min(defaultContentSize.height, 460.0) + + let contentSize: CGSize + if let preferredSize = contentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: defaultContentSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) { + contentSize = preferredSize + } else if let storedContentHeight = contentNode.storedContentHeight { + contentSize = CGSize(width: defaultContentSize.width, height: storedContentHeight) + } else { + contentSize = defaultContentSize + isContentResizeableVertically = true + } + + contentRect = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) * 0.5), y: floor((layout.size.height - contentSize.height) * 0.5)), size: contentSize) contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)) } else { return @@ -752,14 +801,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo transition: contentTransition ) } - if let contentNode = controllerContentNode { - //TODO - contentNode.update( - presentationData: presentationData, - size: CGSize(), - transition: contentTransition - ) - } let actionsConstrainedHeight: CGFloat if let actionsPositionLock = self.actionsStackNode.topPositionLock { @@ -795,6 +836,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo transition: transition ) + if isContentResizeableVertically && self.actionsStackNode.topPositionLock == nil { + var contentHeight = layout.size.height - contentTopInset - contentActionsSpacing - bottomInset - layout.intrinsicInsets.bottom - actionsSize.height + contentHeight = min(contentHeight, contentRect.height) + contentHeight = max(contentHeight, 200.0) + + contentRect = CGRect(origin: CGPoint(x: 12.0, y: floor((layout.size.height - contentHeight) * 0.5)), size: CGSize(width: layout.size.width - 12.0 * 2.0, height: contentHeight)) + } + var isAnimatingOut = false if case .animateOut = stateTransition { isAnimatingOut = true @@ -950,11 +999,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } if let contentNode = controllerContentNode { //TODO: - var contentFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: CGSize(width: 200.0, height: 200.0)) + var contentFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentRect.size) if case let .extracted(extracted) = self.source, extracted.centerVertically, contentFrame.midX > layout.size.width / 2.0 { contentFrame.origin.x = layout.size.width - contentFrame.maxX } contentTransition.updateFrame(node: contentNode, frame: contentFrame, beginWithCurrentState: true) + + contentNode.update( + presentationData: presentationData, + parentLayout: layout, + size: contentFrame.size, + transition: contentTransition + ) } let contentHeight: CGFloat @@ -1005,8 +1061,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.scroller.contentOffset = CGPoint(x: 0.0, y: defaultScrollY) - self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - let animationInContentYDistance: CGFloat let currentContentScreenFrame: CGRect if let contentNode = itemContentNode { @@ -1046,6 +1100,34 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo damping: springDamping, additive: true ) + } else if let contentNode = controllerContentNode { + if case let .controller(source) = self.source, let transitionInfo = source.transitionInfo(), let (sourceView, sourceRect) = transitionInfo.sourceNode() { + let sourcePoint = sourceView.convert(sourceRect.center, to: self.view) + animationInContentYDistance = contentRect.midY - sourcePoint.y + } else { + animationInContentYDistance = 0.0 + } + currentContentScreenFrame = contentRect + + contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + contentNode.layer.animateSpring( + from: -animationInContentYDistance as NSNumber, to: 0.0 as NSNumber, + keyPath: "position.y", + duration: duration, + delay: 0.0, + initialVelocity: 0.0, + damping: springDamping, + additive: true + ) + contentNode.layer.animateSpring( + from: 0.01 as NSNumber, to: 1.0 as NSNumber, + keyPath: "transform.scale", + duration: duration, + delay: 0.0, + initialVelocity: 0.0, + damping: springDamping, + additive: false + ) } else { animationInContentYDistance = 0.0 currentContentScreenFrame = contentRect @@ -1200,8 +1282,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo case let .controller(source): if let putBackInfo = source.transitionInfo() { let _ = putBackInfo - self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + /*self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)*/ //TODO: currentContentScreenFrame = CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)) @@ -1216,7 +1298,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) - let animationInContentYDistance: CGFloat + var animationInContentYDistance: CGFloat switch result { case .default, .custom: @@ -1295,10 +1377,37 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } ) } - if let controllerContentNode { - let _ = controllerContentNode - //TODO - completion() + if let contentNode = controllerContentNode { + if case let .controller(source) = self.source, let transitionInfo = source.transitionInfo(), let (sourceView, sourceRect) = transitionInfo.sourceNode() { + let sourcePoint = sourceView.convert(sourceRect.center, to: self.view) + animationInContentYDistance = contentRect.midY - sourcePoint.y + } else { + animationInContentYDistance = 0.0 + } + + contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.8, removeOnCompletion: false, completion: { _ in + completion() + }) + contentNode.layer.animate( + from: 0.0 as NSNumber, + to: -animationInContentYDistance as NSNumber, + keyPath: "position.y", + timingFunction: timingFunction, + duration: duration, + delay: 0.0, + removeOnCompletion: false, + additive: true + ) + contentNode.layer.animate( + from: 1.0 as NSNumber, + to: 0.01 as NSNumber, + keyPath: "transform.scale", + timingFunction: timingFunction, + duration: duration, + delay: 0.0, + removeOnCompletion: false, + additive: false + ) } self.actionsContainerNode.layer.animateAlpha(from: self.actionsContainerNode.alpha, to: 0.0, duration: duration, removeOnCompletion: false) @@ -1338,8 +1447,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo additive: true ) - self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) - if let reactionContextNode = self.reactionContextNode { reactionContextNode.animateOut(to: currentContentScreenFrame, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) } diff --git a/submodules/ContextUI/Sources/ContextSourceContainer.swift b/submodules/ContextUI/Sources/ContextSourceContainer.swift new file mode 100644 index 0000000000..928612e07e --- /dev/null +++ b/submodules/ContextUI/Sources/ContextSourceContainer.swift @@ -0,0 +1,663 @@ +import Foundation +import AsyncDisplayKit +import Display +import TelegramPresentationData +import SwiftSignalKit +import TelegramCore +import ReactionSelectionNode +import ComponentFlow +import TabSelectorComponent +import ComponentDisplayAdapters + +final class ContextSourceContainer: ASDisplayNode { + final class Source { + weak var controller: ContextController? + + let id: AnyHashable + let title: String + let source: ContextContentSource + + private var _presentationNode: ContextControllerPresentationNode? + var presentationNode: ContextControllerPresentationNode { + return self._presentationNode! + } + + var currentPresentationStateTransition: ContextControllerPresentationNodeStateTransition? + + var validLayout: ContainerViewLayout? + var presentationData: PresentationData? + var delayLayoutUpdate: Bool = false + var isAnimatingOut: Bool = false + + let itemsDisposable = MetaDisposable() + + let ready = Promise() + private let contentReady = Promise() + private let actionsReady = Promise() + + init( + controller: ContextController, + id: AnyHashable, + title: String, + source: ContextContentSource, + items: Signal + ) { + self.controller = controller + self.id = id + self.title = title + self.source = source + + self.ready.set(combineLatest(queue: .mainQueue(), self.contentReady.get(), self.actionsReady.get()) + |> map { a, b -> Bool in + return a && b + } + |> distinctUntilChanged) + + switch source { + case let .location(source): + self.contentReady.set(.single(true)) + + let presentationNode = ContextControllerExtractedPresentationNode( + getController: { [weak self] in + guard let self else { + return nil + } + return self.controller + }, + requestUpdate: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + if let controller = self.controller { + controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) + } + }, + requestDismiss: { [weak self] result in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.dismissedForCancel?() + controller.controllerNode.beginDismiss(result) + }, + requestAnimateOut: { [weak self] result, completion in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.animateOut(result: result, completion: completion) + }, + source: .location(source) + ) + self._presentationNode = presentationNode + case let .reference(source): + self.contentReady.set(.single(true)) + + let presentationNode = ContextControllerExtractedPresentationNode( + getController: { [weak self] in + guard let self else { + return nil + } + return self.controller + }, + requestUpdate: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + if let controller = self.controller { + controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) + } + }, + requestDismiss: { [weak self] result in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.dismissedForCancel?() + controller.controllerNode.beginDismiss(result) + }, + requestAnimateOut: { [weak self] result, completion in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.animateOut(result: result, completion: completion) + }, + source: .reference(source) + ) + self._presentationNode = presentationNode + case let .extracted(source): + self.contentReady.set(.single(true)) + + let presentationNode = ContextControllerExtractedPresentationNode( + getController: { [weak self] in + guard let self else { + return nil + } + return self.controller + }, + requestUpdate: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + if let controller = self.controller { + controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) + } + }, + requestDismiss: { [weak self] result in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.dismissedForCancel?() + controller.controllerNode.beginDismiss(result) + }, + requestAnimateOut: { [weak self] result, completion in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.animateOut(result: result, completion: completion) + }, + source: .extracted(source) + ) + self._presentationNode = presentationNode + case let .controller(source): + self.contentReady.set(source.controller.ready.get()) + + let presentationNode = ContextControllerExtractedPresentationNode( + getController: { [weak self] in + guard let self else { + return nil + } + return self.controller + }, + requestUpdate: { [weak self] transition in + guard let self else { + return + } + self.update(transition: transition) + }, + requestUpdateOverlayWantsToBeBelowKeyboard: { [weak self] transition in + guard let self else { + return + } + if let controller = self.controller { + controller.overlayWantsToBeBelowKeyboardUpdated(transition: transition) + } + }, + requestDismiss: { [weak self] result in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.dismissedForCancel?() + controller.controllerNode.beginDismiss(result) + }, + requestAnimateOut: { [weak self] result, completion in + guard let self, let controller = self.controller else { + return + } + controller.controllerNode.animateOut(result: result, completion: completion) + }, + source: .controller(source) + ) + self._presentationNode = presentationNode + } + + self.itemsDisposable.set((items |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self else { + return + } + + self.setItems(items: items, animated: false) + self.actionsReady.set(.single(true)) + })) + } + + deinit { + self.itemsDisposable.dispose() + } + + func animateIn() { + self.currentPresentationStateTransition = .animateIn + self.update(transition: .animated(duration: 0.5, curve: .spring)) + } + + func animateOut(result: ContextMenuActionResult, completion: @escaping () -> Void) { + self.currentPresentationStateTransition = .animateOut(result: result, completion: completion) + if let _ = self.validLayout { + if case let .custom(transition) = result { + self.delayLayoutUpdate = true + Queue.mainQueue().after(0.1) { + self.delayLayoutUpdate = false + self.update(transition: transition) + self.isAnimatingOut = true + } + } else { + self.update(transition: .animated(duration: 0.35, curve: .easeInOut)) + } + } + } + + func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { + self.presentationNode.addRelativeContentOffset(offset, transition: transition) + } + + func cancelReactionAnimation() { + self.presentationNode.cancelReactionAnimation() + } + + func animateOutToReaction(value: MessageReaction.Reaction, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, reducedCurve: Bool, completion: @escaping () -> Void) { + self.presentationNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, reducedCurve: reducedCurve, completion: completion) + } + + func setItems(items: Signal, animated: Bool) { + self.itemsDisposable.set((items + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self else { + return + } + self.setItems(items: items, animated: animated) + })) + } + + func setItems(items: ContextController.Items, animated: Bool) { + self.presentationNode.replaceItems(items: items, animated: animated) + } + + func pushItems(items: Signal) { + self.itemsDisposable.set((items + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self else { + return + } + self.presentationNode.pushItems(items: items) + })) + } + + func popItems() { + self.presentationNode.popItems() + } + + func update(transition: ContainedViewLayoutTransition) { + guard let validLayout = self.validLayout else { + return + } + guard let presentationData = self.presentationData else { + return + } + self.update(presentationData: presentationData, layout: validLayout, transition: transition) + } + + func update( + presentationData: PresentationData, + layout: ContainerViewLayout, + transition: ContainedViewLayoutTransition + ) { + if self.isAnimatingOut || self.delayLayoutUpdate { + return + } + + self.validLayout = layout + self.presentationData = presentationData + + let presentationStateTransition = self.currentPresentationStateTransition + self.currentPresentationStateTransition = .none + + self.presentationNode.update( + presentationData: presentationData, + layout: layout, + transition: transition, + stateTransition: presentationStateTransition + ) + } + } + + private struct PanState { + var fraction: CGFloat + + init(fraction: CGFloat) { + self.fraction = fraction + } + } + + private weak var controller: ContextController? + + private let backgroundNode: NavigationBackgroundNode + + var sources: [Source] = [] + var activeIndex: Int = 0 + + private var tabSelector: ComponentView? + + private var presentationData: PresentationData? + private var validLayout: ContainerViewLayout? + private var panState: PanState? + + let ready = Promise() + + var activeSource: Source? { + if self.activeIndex >= self.sources.count { + return nil + } + return self.sources[self.activeIndex] + } + + var overlayWantsToBeBelowKeyboard: Bool { + return self.activeSource?.presentationNode.wantsDisplayBelowKeyboard() ?? false + } + + init(controller: ContextController, configuration: ContextController.Configuration) { + self.controller = controller + + self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: false) + + super.init() + + self.addSubnode(self.backgroundNode) + + for i in 0 ..< configuration.sources.count { + let source = configuration.sources[i] + + let mappedSource = Source( + controller: controller, + id: source.id, + title: source.title, + source: source.source, + items: source.items + ) + self.sources.append(mappedSource) + self.addSubnode(mappedSource.presentationNode) + + if source.id == configuration.initialId { + self.activeIndex = i + } + } + + self.ready.set(self.sources[self.activeIndex].ready.get()) + + self.view.addGestureRecognizer(InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in + guard let self else { + return [] + } + if self.sources.count <= 1 { + return [] + } + return [.left, .right] + })) + } + + @objc private func panGesture(_ recognizer: InteractiveTransitionGestureRecognizer) { + switch recognizer.state { + case .began, .changed: + if let validLayout = self.validLayout { + var translationX = recognizer.translation(in: self.view).x + if self.activeIndex == 0 && translationX > 0.0 { + translationX = scrollingRubberBandingOffset(offset: abs(translationX), bandingStart: 0.0, range: 20.0) + } else if self.activeIndex == self.sources.count - 1 && translationX < 0.0 { + translationX = -scrollingRubberBandingOffset(offset: abs(translationX), bandingStart: 0.0, range: 20.0) + } + + self.panState = PanState(fraction: translationX / validLayout.size.width) + self.update(transition: .immediate) + } + case .cancelled, .ended: + if let panState = self.panState { + self.panState = nil + + let velocity = recognizer.velocity(in: self.view) + + var nextIndex = self.activeIndex + if panState.fraction < -0.4 { + nextIndex += 1 + } else if panState.fraction > 0.4 { + nextIndex -= 1 + } else if abs(velocity.x) >= 200.0 { + if velocity.x < 0.0 { + nextIndex += 1 + } else { + nextIndex -= 1 + } + } + if nextIndex < 0 { + nextIndex = 0 + } + if nextIndex > self.sources.count - 1 { + nextIndex = self.sources.count - 1 + } + if nextIndex != self.activeIndex { + self.activeIndex = nextIndex + } + + self.update(transition: .animated(duration: 0.4, curve: .spring)) + } + default: + break + } + } + + func animateIn() { + self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + if let activeSource = self.activeSource { + activeSource.animateIn() + } + if let tabSelectorView = self.tabSelector?.view { + tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + + func animateOut(result: ContextMenuActionResult, completion: @escaping () -> Void) { + self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + + if let tabSelectorView = self.tabSelector?.view { + tabSelectorView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + + if let activeSource = self.activeSource { + activeSource.animateOut(result: result, completion: completion) + } else { + completion() + } + } + + func highlightGestureMoved(location: CGPoint, hover: Bool) { + if self.activeIndex >= self.sources.count { + return + } + self.sources[self.activeIndex].presentationNode.highlightGestureMoved(location: location, hover: hover) + } + + func highlightGestureFinished(performAction: Bool) { + if self.activeIndex >= self.sources.count { + return + } + self.sources[self.activeIndex].presentationNode.highlightGestureFinished(performAction: performAction) + } + + func performHighlightedAction() { + self.activeSource?.presentationNode.highlightGestureFinished(performAction: true) + } + + func decreaseHighlightedIndex() { + self.activeSource?.presentationNode.decreaseHighlightedIndex() + } + + func increaseHighlightedIndex() { + self.activeSource?.presentationNode.increaseHighlightedIndex() + } + + func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) { + if let activeSource = self.activeSource { + activeSource.addRelativeContentOffset(offset, transition: transition) + } + } + + func cancelReactionAnimation() { + if let activeSource = self.activeSource { + activeSource.cancelReactionAnimation() + } + } + + func animateOutToReaction(value: MessageReaction.Reaction, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, reducedCurve: Bool, completion: @escaping () -> Void) { + if let activeSource = self.activeSource { + activeSource.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, animateTargetContainer: animateTargetContainer, addStandaloneReactionAnimation: addStandaloneReactionAnimation, reducedCurve: reducedCurve, completion: completion) + } else { + completion() + } + } + + func setItems(items: Signal, animated: Bool) { + if let activeSource = self.activeSource { + activeSource.setItems(items: items, animated: animated) + } + } + + func pushItems(items: Signal) { + if let activeSource = self.activeSource { + activeSource.pushItems(items: items) + } + } + + func popItems() { + if let activeSource = self.activeSource { + activeSource.popItems() + } + } + + private func update(transition: ContainedViewLayoutTransition) { + if let presentationData = self.presentationData, let validLayout = self.validLayout { + self.update(presentationData: presentationData, layout: validLayout, transition: transition) + } + } + + func update( + presentationData: PresentationData, + layout: ContainerViewLayout, + transition: ContainedViewLayoutTransition + ) { + self.presentationData = presentationData + self.validLayout = layout + + var childLayout = layout + + if let activeSource = self.activeSource { + switch activeSource.source { + case .location, .reference: + self.backgroundNode.updateColor( + color: .clear, + enableBlur: false, + forceKeepBlur: false, + transition: .immediate + ) + case .extracted: + self.backgroundNode.updateColor( + color: presentationData.theme.contextMenu.dimColor, + enableBlur: true, + forceKeepBlur: true, + transition: .immediate + ) + case .controller: + self.backgroundNode.updateColor( + color: presentationData.theme.contextMenu.dimColor, + enableBlur: true, + forceKeepBlur: true, + transition: .immediate + ) + } + } + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true) + self.backgroundNode.update(size: layout.size, transition: transition) + + if self.sources.count > 1 { + let tabSelector: ComponentView + if let current = self.tabSelector { + tabSelector = current + } else { + tabSelector = ComponentView() + self.tabSelector = tabSelector + } + let mappedItems = self.sources.map { source -> TabSelectorComponent.Item in + return TabSelectorComponent.Item(id: source.id, title: source.title) + } + let tabSelectorSize = tabSelector.update( + transition: Transition(transition), + component: AnyComponent(TabSelectorComponent( + colors: TabSelectorComponent.Colors( + foreground: presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.8), + selection: presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.1) + ), + customLayout: TabSelectorComponent.CustomLayout( + spacing: 9.0 + ), + items: mappedItems, + selectedId: self.activeSource?.id, + setSelectedId: { [weak self] id in + guard let self else { + return + } + if let index = self.sources.firstIndex(where: { $0.id == id }) { + self.activeIndex = index + self.update(transition: .animated(duration: 0.4, curve: .spring)) + } + } + )), + environment: {}, + containerSize: CGSize(width: layout.size.width, height: 44.0) + ) + childLayout.intrinsicInsets.bottom += 30.0 + + if let tabSelectorView = tabSelector.view { + if tabSelectorView.superview == nil { + self.view.addSubview(tabSelectorView) + } + transition.updateFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - tabSelectorSize.width) * 0.5), y: layout.size.height - layout.intrinsicInsets.bottom - tabSelectorSize.height), size: tabSelectorSize)) + } + } else if let tabSelector = self.tabSelector { + self.tabSelector = nil + tabSelector.view?.removeFromSuperview() + } + + for i in 0 ..< self.sources.count { + var itemFrame = CGRect(origin: CGPoint(), size: childLayout.size) + itemFrame.origin.x += CGFloat(i - self.activeIndex) * childLayout.size.width + if let panState = self.panState { + itemFrame.origin.x += panState.fraction * childLayout.size.width + } + + let itemTransition = transition + itemTransition.updateFrame(node: self.sources[i].presentationNode, frame: itemFrame) + self.sources[i].update( + presentationData: presentationData, + layout: childLayout, + transition: itemTransition + ) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let tabSelectorView = self.tabSelector?.view { + if let result = tabSelectorView.hitTest(self.view.convert(point, to: tabSelectorView), with: event) { + return result + } + } + + guard let activeSource = self.activeSource else { + return nil + } + return activeSource.presentationNode.view.hitTest(point, with: event) + } +} diff --git a/submodules/ContextUI/Sources/PeekController.swift b/submodules/ContextUI/Sources/PeekController.swift index 615979d884..a561c2278c 100644 --- a/submodules/ContextUI/Sources/PeekController.swift +++ b/submodules/ContextUI/Sources/PeekController.swift @@ -38,7 +38,7 @@ public final class PeekController: ViewController, ContextControllerProtocol { return nil } - public func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?) { + public func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?, animated: Bool) { } public func setItems(_ items: Signal, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) { diff --git a/submodules/Display/Source/ContextMenuController.swift b/submodules/Display/Source/ContextMenuController.swift index 4241125737..19ce500045 100644 --- a/submodules/Display/Source/ContextMenuController.swift +++ b/submodules/Display/Source/ContextMenuController.swift @@ -28,6 +28,7 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder, private let catchTapsOutside: Bool private let hasHapticFeedback: Bool private let blurred: Bool + private let skipCoordnateConversion: Bool private var layout: ContainerViewLayout? @@ -36,11 +37,12 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder, public var dismissOnTap: ((UIView, CGPoint) -> Bool)? - public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false, blurred: Bool = false) { + public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false, blurred: Bool = false, skipCoordnateConversion: Bool = false) { self.actions = actions self.catchTapsOutside = catchTapsOutside self.hasHapticFeedback = hasHapticFeedback self.blurred = blurred + self.skipCoordnateConversion = skipCoordnateConversion super.init(navigationBarPresentationData: nil) @@ -92,8 +94,13 @@ public final class ContextMenuController: ViewController, KeyShortcutResponder, self.layout = layout if let presentationArguments = self.presentationArguments as? ContextMenuControllerPresentationArguments, let (sourceNode, sourceRect, containerNode, containerRect) = presentationArguments.sourceNodeAndRect() { - self.contextMenuNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) - self.contextMenuNode.containerRect = containerNode.view.convert(containerRect, to: nil) + if self.skipCoordnateConversion { + self.contextMenuNode.sourceRect = sourceRect + self.contextMenuNode.containerRect = containerRect + } else { + self.contextMenuNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) + self.contextMenuNode.containerRect = containerNode.view.convert(containerRect, to: nil) + } } else { self.contextMenuNode.sourceRect = nil self.contextMenuNode.containerRect = nil diff --git a/submodules/Display/Source/ContextMenuNode.swift b/submodules/Display/Source/ContextMenuNode.swift index b445b67bf1..9f1168ce25 100644 --- a/submodules/Display/Source/ContextMenuNode.swift +++ b/submodules/Display/Source/ContextMenuNode.swift @@ -146,7 +146,7 @@ final class ContextMenuNode: ASDisplayNode { private let feedback: HapticFeedback? - init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, dismissOnTap: @escaping (UIView, CGPoint) -> Bool, catchTapsOutside: Bool, hasHapticFeedback: Bool = false, blurred: Bool = false) { + init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, dismissOnTap: @escaping (UIView, CGPoint) -> Bool, catchTapsOutside: Bool, hasHapticFeedback: Bool, blurred: Bool = false) { self.actions = actions self.dismiss = dismiss self.dismissOnTap = dismissOnTap diff --git a/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift b/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift index 6927cbc1b2..8b72c2f7a0 100644 --- a/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift +++ b/submodules/Display/Source/InteractiveTransitionGestureRecognizer.swift @@ -140,7 +140,7 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { override public func translation(in view: UIView?) -> CGPoint { let result = super.translation(in: view) - return result.offsetBy(dx: self.ignoreOffset.x, dy: self.ignoreOffset.y) + return result//.offsetBy(dx: self.ignoreOffset.x, dy: self.ignoreOffset.y) } override public func touchesMoved(_ touches: Set, with event: UIEvent) { diff --git a/submodules/Display/Source/PresentationContext.swift b/submodules/Display/Source/PresentationContext.swift index b396a44582..804cabd50a 100644 --- a/submodules/Display/Source/PresentationContext.swift +++ b/submodules/Display/Source/PresentationContext.swift @@ -306,7 +306,7 @@ public final class PresentationContext { UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: nil) } - func hitTest(view: UIView, point: CGPoint, with event: UIEvent?) -> UIView? { + public func hitTest(view: UIView, point: CGPoint, with event: UIEvent?) -> UIView? { for (controller, _) in self.controllers.reversed() { if controller.isViewLoaded { if let result = controller.view.hitTest(view.convert(point, to: controller.view), with: event) { diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index de32a60afb..7fad5b1539 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -1199,9 +1199,12 @@ open class TextNode: ASDisplayNode { let rawSubstring = segment.substring.string as NSString let substringLength = rawSubstring.length - let typesetter = CTTypesetterCreateWithAttributedString(segment.substring as CFAttributedString) - var currentLineStartIndex = 0 + let segmentTypesetterString = attributedString.attributedSubstring(from: NSRange(location: 0, length: segment.firstCharacterOffset + substringLength)) + let typesetter = CTTypesetterCreateWithAttributedString(segmentTypesetterString as CFAttributedString) + + var currentLineStartIndex = segment.firstCharacterOffset + let segmentEndIndex = segment.firstCharacterOffset + substringLength var constrainedSegmentWidth = constrainedSize.width var additionalOffsetX: CGFloat = 0.0 @@ -1250,7 +1253,7 @@ open class TextNode: ASDisplayNode { frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)), ascent: lineAscent, descent: lineDescent, - range: NSRange(location: segment.firstCharacterOffset + currentLineStartIndex, length: lineCharacterCount), + range: NSRange(location: currentLineStartIndex, length: lineCharacterCount), isRTL: false, strikethroughs: [], spoilers: [], @@ -1265,7 +1268,7 @@ open class TextNode: ASDisplayNode { currentLineStartIndex += lineCharacterCount - if currentLineStartIndex >= substringLength { + if currentLineStartIndex >= segmentEndIndex { break } } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index ddcfa18ae2..fd8eb1795f 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -2505,7 +2505,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return } - c.setItems(strongSelf.contextMenuSpeedItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuSpeedItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) items.append(.separator) @@ -2634,7 +2634,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { c.dismiss(completion: nil) return } - c.setItems(strongSelf.contextMenuMainItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuMainItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) let sliderValuePromise = ValuePromise(nil) diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index d6323cd19c..bbc30be749 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -632,7 +632,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size) let item: InstantPageItem if let url = url, let coverId = coverId, case let .image(image) = media[coverId] { - let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, story: nil, attributes: [], instantPage: nil) + let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, story: nil, attributes: [], instantPage: nil, displayOptions: .default) let content = TelegramMediaWebpageContent.Loaded(loadedContent) item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: .webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content)), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false) diff --git a/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift b/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift index edf765de7f..6bc400325a 100644 --- a/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift +++ b/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift @@ -158,6 +158,10 @@ final class MediaBoxFileContextV2Impl: MediaBoxFileContext { } } + deinit { + self.pendingFetch?.disposable.dispose() + } + func request( range: Range, isFullRange: Bool, diff --git a/submodules/Postbox/Sources/PostboxLogging.swift b/submodules/Postbox/Sources/PostboxLogging.swift index 6daff29cfa..8faf111fc0 100644 --- a/submodules/Postbox/Sources/PostboxLogging.swift +++ b/submodules/Postbox/Sources/PostboxLogging.swift @@ -1,11 +1,17 @@ import Foundation private var postboxLogger: (String) -> Void = { _ in } +private var postboxLoggerSync: () -> Void = {} -public func setPostboxLogger(_ f: @escaping (String) -> Void) { +public func setPostboxLogger(_ f: @escaping (String) -> Void, sync: @escaping () -> Void) { postboxLogger = f + postboxLoggerSync = sync } public func postboxLog(_ what: @autoclosure () -> String) { postboxLogger(what()) } + +public func postboxLogSync() { + postboxLoggerSync() +} diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index 7b63cd186d..317cdca45d 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -60,6 +60,7 @@ struct SqlitePreparedStatement { if let path = pathToRemoveOnError { postboxLog("Corrupted DB at step, dropping") try? FileManager.default.removeItem(atPath: path) + postboxLogSync() preconditionFailure() } } @@ -84,6 +85,7 @@ struct SqlitePreparedStatement { if let path = pathToRemoveOnError { postboxLog("Corrupted DB at step, dropping") try? FileManager.default.removeItem(atPath: path) + postboxLogSync() preconditionFailure() } } @@ -300,12 +302,14 @@ public final class SqliteValueBox: ValueBox { } catch { let _ = try? FileManager.default.removeItem(atPath: tempPath) postboxLog("Don't have write access to database folder") + postboxLogSync() preconditionFailure("Don't have write access to database folder") } if self.removeDatabaseOnError { let _ = try? FileManager.default.removeItem(atPath: path) } + postboxLogSync() preconditionFailure("Couldn't open database") } @@ -577,6 +581,7 @@ public final class SqliteValueBox: ValueBox { try? FileManager.default.removeItem(atPath: databasePath) } + postboxLogSync() preconditionFailure() } }) @@ -1197,6 +1202,7 @@ public final class SqliteValueBox: ValueBox { let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil) if status != SQLITE_OK { let errorText = self.database.currentError() ?? "Unknown error" + postboxLogSync() preconditionFailure(errorText) } let preparedStatement = SqlitePreparedStatement(statement: statement) @@ -1211,6 +1217,7 @@ public final class SqliteValueBox: ValueBox { let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil) if status != SQLITE_OK { let errorText = self.database.currentError() ?? "Unknown error" + postboxLogSync() preconditionFailure(errorText) } let preparedStatement = SqlitePreparedStatement(statement: statement) @@ -1250,6 +1257,7 @@ public final class SqliteValueBox: ValueBox { let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?) ON CONFLICT(key) DO NOTHING", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil) if status != SQLITE_OK { let errorText = self.database.currentError() ?? "Unknown error" + postboxLogSync() preconditionFailure(errorText) } let preparedStatement = SqlitePreparedStatement(statement: statement) @@ -1264,6 +1272,7 @@ public final class SqliteValueBox: ValueBox { let status = sqlite3_prepare_v3(self.database.handle, "INSERT INTO t\(table.table.id) (key, value) VALUES(?, ?)", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil) if status != SQLITE_OK { let errorText = self.database.currentError() ?? "Unknown error" + postboxLogSync() preconditionFailure(errorText) } let preparedStatement = SqlitePreparedStatement(statement: statement) @@ -2297,6 +2306,7 @@ public final class SqliteValueBox: ValueBox { self.clearStatements() if self.isReadOnly { + postboxLogSync() preconditionFailure() } @@ -2346,6 +2356,7 @@ public final class SqliteValueBox: ValueBox { private func reencryptInPlace(database: Database, encryptionParameters: ValueBoxEncryptionParameters) -> Database { if self.isReadOnly { + postboxLogSync() preconditionFailure() } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 35cfa09182..18dd6a9275 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -211,7 +211,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1815593308] = { return Api.DocumentAttribute.parse_documentAttributeImageSize($0) } dict[1662637586] = { return Api.DocumentAttribute.parse_documentAttributeSticker($0) } dict[-745541182] = { return Api.DocumentAttribute.parse_documentAttributeVideo($0) } - dict[-1783606645] = { return Api.DraftMessage.parse_draftMessage($0) } + dict[-620277848] = { return Api.DraftMessage.parse_draftMessage($0) } dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) } dict[-1764723459] = { return Api.EmailVerification.parse_emailVerificationApple($0) } dict[-1842457175] = { return Api.EmailVerification.parse_emailVerificationCode($0) } @@ -343,6 +343,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1530447553] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) } dict[505969924] = { return Api.InputMedia.parse_inputMediaUploadedPhoto($0) } dict[-1052959727] = { return Api.InputMedia.parse_inputMediaVenue($0) } + dict[-1038383031] = { return Api.InputMedia.parse_inputMediaWebPage($0) } dict[-1392895362] = { return Api.InputMessage.parse_inputMessageCallbackQuery($0) } dict[-1502174430] = { return Api.InputMessage.parse_inputMessageID($0) } dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) } @@ -482,6 +483,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) } dict[-758129906] = { return Api.MessageAction.parse_messageActionGiftCode($0) } dict[-935499028] = { return Api.MessageAction.parse_messageActionGiftPremium($0) } + dict[858499565] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($0) } dict[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) } dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) } dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) } @@ -542,7 +544,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1758159491] = { return Api.MessageMedia.parse_messageMediaStory($0) } dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) } dict[784356159] = { return Api.MessageMedia.parse_messageMediaVenue($0) } - dict[-1557277184] = { return Api.MessageMedia.parse_messageMediaWebPage($0) } + dict[-571405253] = { return Api.MessageMedia.parse_messageMediaWebPage($0) } dict[-1938180548] = { return Api.MessagePeerReaction.parse_messagePeerReaction($0) } dict[-1228133028] = { return Api.MessagePeerVote.parse_messagePeerVote($0) } dict[1959634180] = { return Api.MessagePeerVote.parse_messagePeerVoteInputOption($0) } @@ -550,7 +552,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[182649427] = { return Api.MessageRange.parse_messageRange($0) } dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) } dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) } - dict[1029445267] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } + dict[1860946621] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[-1667711039] = { return Api.MessageReplyHeader.parse_messageReplyStoryHeader($0) } dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[975236280] = { return Api.MessagesFilter.parse_inputMessagesFilterChatPhotos($0) } @@ -975,9 +977,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[475467473] = { return Api.WebDocument.parse_webDocument($0) } dict[-104284986] = { return Api.WebDocument.parse_webDocumentNoProxy($0) } dict[-392411726] = { return Api.WebPage.parse_webPage($0) } - dict[-350980120] = { return Api.WebPage.parse_webPageEmpty($0) } + dict[555358088] = { return Api.WebPage.parse_webPageEmpty($0) } dict[1930545681] = { return Api.WebPage.parse_webPageNotModified($0) } - dict[-981018084] = { return Api.WebPage.parse_webPagePending($0) } + dict[-1328464313] = { return Api.WebPage.parse_webPagePending($0) } dict[781501415] = { return Api.WebPageAttribute.parse_webPageAttributeStory($0) } dict[1421174295] = { return Api.WebPageAttribute.parse_webPageAttributeTheme($0) } dict[211046684] = { return Api.WebViewMessageSent.parse_webViewMessageSent($0) } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index fb0a25191d..4b6509524c 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -611,6 +611,7 @@ public extension Api { case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String) case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?) + case messageActionGiveawayLaunch case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?) case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32) case messageActionHistoryClear @@ -771,6 +772,12 @@ public extension Api { serializeInt32(months, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} + break + case .messageActionGiveawayLaunch: + if boxed { + buffer.appendInt32(858499565) + } + break case .messageActionGroupCall(let flags, let call, let duration): if boxed { @@ -981,6 +988,8 @@ public extension Api { return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any)]) case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) + case .messageActionGiveawayLaunch: + return ("messageActionGiveawayLaunch", []) case .messageActionGroupCall(let flags, let call, let duration): return ("messageActionGroupCall", [("flags", flags as Any), ("call", call as Any), ("duration", duration as Any)]) case .messageActionGroupCallScheduled(let call, let scheduleDate): @@ -1262,6 +1271,9 @@ public extension Api { return nil } } + public static func parse_messageActionGiveawayLaunch(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionGiveawayLaunch + } public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api13.swift b/submodules/TelegramApi/Sources/Api13.swift index a63b9f0b20..bfeed9804a 100644 --- a/submodules/TelegramApi/Sources/Api13.swift +++ b/submodules/TelegramApi/Sources/Api13.swift @@ -748,7 +748,7 @@ public extension Api { case messageMediaStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) case messageMediaUnsupported case messageMediaVenue(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) - case messageMediaWebPage(webpage: Api.WebPage) + case messageMediaWebPage(flags: Int32, webpage: Api.WebPage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -880,10 +880,11 @@ public extension Api { serializeString(venueId, buffer: buffer, boxed: false) serializeString(venueType, buffer: buffer, boxed: false) break - case .messageMediaWebPage(let webpage): + case .messageMediaWebPage(let flags, let webpage): if boxed { - buffer.appendInt32(-1557277184) + buffer.appendInt32(-571405253) } + serializeInt32(flags, buffer: buffer, boxed: false) webpage.serialize(buffer, true) break } @@ -919,8 +920,8 @@ public extension Api { return ("messageMediaUnsupported", []) case .messageMediaVenue(let geo, let title, let address, let provider, let venueId, let venueType): return ("messageMediaVenue", [("geo", geo as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) - case .messageMediaWebPage(let webpage): - return ("messageMediaWebPage", [("webpage", webpage as Any)]) + case .messageMediaWebPage(let flags, let webpage): + return ("messageMediaWebPage", [("flags", flags as Any), ("webpage", webpage as Any)]) } } @@ -1201,13 +1202,16 @@ public extension Api { } } public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.WebPage? + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.WebPage? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.WebPage + _2 = Api.parse(reader, signature: signature) as? Api.WebPage } let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaWebPage(webpage: _1!) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaWebPage(flags: _1!, webpage: _2!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index 191530d917..57804e6d00 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -321,20 +321,21 @@ public extension Api { } } public extension Api { - enum MessageReplyHeader: TypeConstructorDescription { - case messageReplyHeader(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyHeader: Api.MessageFwdHeader?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?) + indirect enum MessageReplyHeader: TypeConstructorDescription { + case messageReplyHeader(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyFrom: Api.MessageFwdHeader?, replyMedia: Api.MessageMedia?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?) case messageReplyStoryHeader(userId: Int64, storyId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyHeader, let replyToTopId, let quoteText, let quoteEntities): + case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities): if boxed { - buffer.appendInt32(1029445267) + buffer.appendInt32(1860946621) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 4) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {replyHeader!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {replyFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 8) != 0 {replyMedia!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 6) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) @@ -355,8 +356,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyHeader, let replyToTopId, let quoteText, let quoteEntities): - return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyHeader", replyHeader as Any), ("replyToTopId", replyToTopId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any)]) + case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyFrom, let replyMedia, let replyToTopId, let quoteText, let quoteEntities): + return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyFrom", replyFrom as Any), ("replyMedia", replyMedia as Any), ("replyToTopId", replyToTopId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any)]) case .messageReplyStoryHeader(let userId, let storyId): return ("messageReplyStoryHeader", [("userId", userId as Any), ("storyId", storyId as Any)]) } @@ -375,23 +376,28 @@ public extension Api { if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader } } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } - var _6: String? - if Int(_1!) & Int(1 << 6) != 0 {_6 = parseString(reader) } - var _7: [Api.MessageEntity]? + var _5: Api.MessageMedia? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _6: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt32() } + var _7: String? + if Int(_1!) & Int(1 << 6) != 0 {_7 = parseString(reader) } + var _8: [Api.MessageEntity]? if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyHeader: _4, replyToTopId: _5, quoteText: _6, quoteEntities: _7) + let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index b99387ca58..fc55558b3d 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -1369,9 +1369,9 @@ public extension Api { public extension Api { enum WebPage: TypeConstructorDescription { case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) - case webPageEmpty(id: Int64) + case webPageEmpty(flags: Int32, id: Int64, url: String?) case webPageNotModified(flags: Int32, cachedPageViews: Int32?) - case webPagePending(id: Int64, date: Int32) + case webPagePending(flags: Int32, id: Int64, url: String?, date: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1403,11 +1403,13 @@ public extension Api { item.serialize(buffer, true) }} break - case .webPageEmpty(let id): + case .webPageEmpty(let flags, let id, let url): if boxed { - buffer.appendInt32(-350980120) + buffer.appendInt32(555358088) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} break case .webPageNotModified(let flags, let cachedPageViews): if boxed { @@ -1416,11 +1418,13 @@ public extension Api { serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(cachedPageViews!, buffer: buffer, boxed: false)} break - case .webPagePending(let id, let date): + case .webPagePending(let flags, let id, let url, let date): if boxed { - buffer.appendInt32(-981018084) + buffer.appendInt32(-1328464313) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} serializeInt32(date, buffer: buffer, boxed: false) break } @@ -1430,12 +1434,12 @@ public extension Api { switch self { case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): return ("webPage", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("displayUrl", displayUrl as Any), ("hash", hash as Any), ("type", type as Any), ("siteName", siteName as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("embedUrl", embedUrl as Any), ("embedType", embedType as Any), ("embedWidth", embedWidth as Any), ("embedHeight", embedHeight as Any), ("duration", duration as Any), ("author", author as Any), ("document", document as Any), ("cachedPage", cachedPage as Any), ("attributes", attributes as Any)]) - case .webPageEmpty(let id): - return ("webPageEmpty", [("id", id as Any)]) + case .webPageEmpty(let flags, let id, let url): + return ("webPageEmpty", [("flags", flags as Any), ("id", id as Any), ("url", url as Any)]) case .webPageNotModified(let flags, let cachedPageViews): return ("webPageNotModified", [("flags", flags as Any), ("cachedPageViews", cachedPageViews as Any)]) - case .webPagePending(let id, let date): - return ("webPagePending", [("id", id as Any), ("date", date as Any)]) + case .webPagePending(let flags, let id, let url, let date): + return ("webPagePending", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("date", date as Any)]) } } @@ -1513,11 +1517,17 @@ public extension Api { } } public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } let _c1 = _1 != nil - if _c1 { - return Api.WebPage.webPageEmpty(id: _1!) + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) } else { return nil @@ -1538,14 +1548,20 @@ public extension Api { } } public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebPage.webPagePending(id: _1!, date: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 9754b32c13..578458183d 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1035,7 +1035,7 @@ public extension Api.stories { } } public extension Api.updates { - enum ChannelDifference: TypeConstructorDescription { + indirect enum ChannelDifference: TypeConstructorDescription { case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User]) case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?) case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index 57b8d8fb8d..8638522d69 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -6447,11 +6447,11 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1688404588) + buffer.appendInt32(2146678790) serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 4) != 0 {replyTo!.serialize(buffer, true)} peer.serialize(buffer, true) serializeString(message, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) @@ -6459,7 +6459,8 @@ public extension Api.functions.messages { for item in entities! { item.serialize(buffer, true) }} - return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", String(describing: flags)), ("replyTo", String(describing: replyTo)), ("peer", String(describing: peer)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + if Int(flags) & Int(1 << 5) != 0 {media!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", String(describing: flags)), ("replyTo", String(describing: replyTo)), ("peer", String(describing: peer)), ("message", String(describing: message)), ("entities", String(describing: entities)), ("media", String(describing: media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index c73745ecd0..9a71f34213 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -871,7 +871,7 @@ public extension Api { } } public extension Api { - enum Dialog: TypeConstructorDescription { + indirect enum Dialog: TypeConstructorDescription { case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?, ttlPeriod: Int32?) case dialogFolder(flags: Int32, folder: Api.Folder, peer: Api.Peer, topMessage: Int32, unreadMutedPeersCount: Int32, unreadUnmutedPeersCount: Int32, unreadMutedMessagesCount: Int32, unreadUnmutedMessagesCount: Int32) diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index 3695b295e6..8918bec605 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -1,13 +1,13 @@ public extension Api { - enum DraftMessage: TypeConstructorDescription { - case draftMessage(flags: Int32, replyTo: Api.MessageReplyHeader?, message: String, entities: [Api.MessageEntity]?, date: Int32) + indirect enum DraftMessage: TypeConstructorDescription { + case draftMessage(flags: Int32, replyTo: Api.MessageReplyHeader?, message: String, entities: [Api.MessageEntity]?, media: Api.MessageMedia?, date: Int32) case draftMessageEmpty(flags: Int32, date: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .draftMessage(let flags, let replyTo, let message, let entities, let date): + case .draftMessage(let flags, let replyTo, let message, let entities, let media, let date): if boxed { - buffer.appendInt32(-1783606645) + buffer.appendInt32(-620277848) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 4) != 0 {replyTo!.serialize(buffer, true)} @@ -17,6 +17,7 @@ public extension Api { for item in entities! { item.serialize(buffer, true) }} + if Int(flags) & Int(1 << 5) != 0 {media!.serialize(buffer, true)} serializeInt32(date, buffer: buffer, boxed: false) break case .draftMessageEmpty(let flags, let date): @@ -31,8 +32,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .draftMessage(let flags, let replyTo, let message, let entities, let date): - return ("draftMessage", [("flags", flags as Any), ("replyTo", replyTo as Any), ("message", message as Any), ("entities", entities as Any), ("date", date as Any)]) + case .draftMessage(let flags, let replyTo, let message, let entities, let media, let date): + return ("draftMessage", [("flags", flags as Any), ("replyTo", replyTo as Any), ("message", message as Any), ("entities", entities as Any), ("media", media as Any), ("date", date as Any)]) case .draftMessageEmpty(let flags, let date): return ("draftMessageEmpty", [("flags", flags as Any), ("date", date as Any)]) } @@ -51,15 +52,20 @@ public extension Api { if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } - var _5: Int32? - _5 = reader.readInt32() + var _5: Api.MessageMedia? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.DraftMessage.draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, date: _5!) + let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.DraftMessage.draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, media: _5, date: _6!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 6c3196fbe2..7416cdbc5b 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -211,7 +211,7 @@ public extension Api { } } public extension Api { - enum ForumTopic: TypeConstructorDescription { + indirect enum ForumTopic: TypeConstructorDescription { case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, fromId: Api.Peer, notifySettings: Api.PeerNotifySettings, draft: Api.DraftMessage?) case forumTopicDeleted(id: Int32) diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 7cb7297b2a..8048250b31 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -314,6 +314,7 @@ public extension Api { case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, ttlSeconds: Int32?) case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?) case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) + case inputMediaWebPage(flags: Int32, url: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -477,6 +478,13 @@ public extension Api { serializeString(venueId, buffer: buffer, boxed: false) serializeString(venueType, buffer: buffer, boxed: false) break + case .inputMediaWebPage(let flags, let url): + if boxed { + buffer.appendInt32(-1038383031) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break } } @@ -514,6 +522,8 @@ public extension Api { return ("inputMediaUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType): return ("inputMediaVenue", [("geoPoint", geoPoint as Any), ("title", title as Any), ("address", address as Any), ("provider", provider as Any), ("venueId", venueId as Any), ("venueType", venueType as Any)]) + case .inputMediaWebPage(let flags, let url): + return ("inputMediaWebPage", [("flags", flags as Any), ("url", url as Any)]) } } @@ -857,6 +867,20 @@ public extension Api { return nil } } + public static func parse_inputMediaWebPage(_ reader: BufferReader) -> InputMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputMedia.inputMediaWebPage(flags: _1!, url: _2!) + } + else { + return nil + } + } } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index ec1abfe31d..f00ffed5b5 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -2517,7 +2517,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuDisplayAsItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuDisplayAsItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) items.append(.separator) break @@ -2550,7 +2550,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuAudioItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuAudioItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) } @@ -2587,7 +2587,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuPermissionItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuPermissionItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) } } @@ -2865,7 +2865,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) return .single(items) } @@ -2960,7 +2960,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) return items } @@ -3006,7 +3006,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController guard let strongSelf = self else { return } - c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) }))) } return .single(items) diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 72122f3c07..2d474719ce 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -202,6 +202,7 @@ private var declaredEncodables: Void = { declareEncodable(SynchronizePeerStoriesOperation.self, f: { SynchronizePeerStoriesOperation(decoder: $0) }) declareEncodable(MapVenue.self, f: { MapVenue(decoder: $0) }) declareEncodable(TelegramMediaGiveaway.self, f: { TelegramMediaGiveaway(decoder: $0) }) + declareEncodable(WebpagePreviewMessageAttribute.self, f: { WebpagePreviewMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift b/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift index 14e1439d5a..932933000d 100644 --- a/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift +++ b/submodules/TelegramCore/Sources/AccountManager/AccountManagerImpl.swift @@ -71,6 +71,7 @@ final class AccountManagerImpl { return (atomicState.records.sorted(by: { $0.key.int64 < $1.key.int64 }).map({ $1 }), atomicState.currentRecordId) } catch let e { postboxLog("decode atomic state error: \(e)") + postboxLogSync() preconditionFailure() } } @@ -85,10 +86,16 @@ final class AccountManagerImpl { self.temporarySessionId = temporarySessionId let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil) guard let guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, isTemporary: isTemporary, isReadOnly: false, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: nil, upgradeProgress: { _ in }) else { + postboxLog("Could not open guard value box at \(basePath + "/guard_db")") + postboxLogSync() + preconditionFailure() return nil } self.guardValueBox = guardValueBox guard let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: nil, upgradeProgress: { _ in }) else { + postboxLog("Could not open value box at \(basePath + "/db")") + postboxLogSync() + preconditionFailure() return nil } self.valueBox = valueBox @@ -106,6 +113,7 @@ final class AccountManagerImpl { } catch let e { postboxLog("decode atomic state error: \(e)") let _ = try? FileManager.default.removeItem(atPath: self.atomicStatePath) + postboxLogSync() preconditionFailure() } } catch let e { @@ -246,9 +254,11 @@ final class AccountManagerImpl { if let data = try? JSONEncoder().encode(self.currentAtomicState) { if let _ = try? data.write(to: URL(fileURLWithPath: self.atomicStatePath), options: [.atomic]) { } else { + postboxLogSync() preconditionFailure() } } else { + postboxLogSync() preconditionFailure() } } @@ -523,6 +533,7 @@ public final class AccountManager { if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, temporarySessionId: temporarySessionId) { return value } else { + postboxLogSync() preconditionFailure() } }) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index feb79c5ea9..196abc347d 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -217,7 +217,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionSetSameChatWallPaper: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionSetSameChatWallPaper, .messageActionGiveawayLaunch: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) @@ -261,7 +261,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere let peerId: PeerId = chatPeerId.peerId switch replyTo { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities): + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities): let _ = replyHeader let _ = replyToTopId let _ = quoteText @@ -282,7 +282,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _): if let replyHeader = replyHeader { switch replyHeader { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities): + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities): let _ = replyHeader let _ = replyToTopId let _ = quoteText @@ -334,7 +334,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI } else { return (TelegramMediaExpiredContent(data: .file), nil, nil, nil) } - case let .messageMediaWebPage(webpage): + case let .messageMediaWebPage(_, webpage): if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) { return (mediaWebpage, nil, nil, nil) } @@ -567,7 +567,7 @@ extension StoreMessage { if let replyTo = replyTo { var threadMessageId: MessageId? switch replyTo { - case let .messageReplyHeader(flags, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities): + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities): let isForumTopic = (flags & (1 << 3)) != 0 var quote: EngineMessageReplyQuote? @@ -841,7 +841,7 @@ extension StoreMessage { if let replyTo = replyTo { var threadMessageId: MessageId? switch replyTo { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities): + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities): var quote: EngineMessageReplyQuote? if let quoteText = quoteText { quote = EngineMessageReplyQuote(text: quoteText, entities: messageTextEntitiesFromApiEntities(quoteEntities ?? [])) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index f7ae9582bc..dc4b23622e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -129,6 +129,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper))) case let .messageActionGiftCode(flags, boostPeer, months, slug): return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, boostPeerId: boostPeer?.peerId, months: months)) + case .messageActionGiveawayLaunch: + return TelegramMediaAction(action: .giveawayLaunched) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift index 66c30a8751..c2196cbeea 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift @@ -21,7 +21,8 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> switch webpage { case .webPageNotModified: return nil - case let .webPagePending(id, date): + case let .webPagePending(flags, id, url, date): + let _ = flags return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url)) case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage, attributes): var embedSize: PixelDimensions? @@ -55,8 +56,26 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> if let cachedPage = cachedPage { instantPage = InstantPage(apiPage: cachedPage) } - return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, image: image, file: file, story: story, attributes: webpageAttributes, instantPage: instantPage))) + return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, image: image, file: file, story: story, attributes: webpageAttributes, instantPage: instantPage, displayOptions: .default))) case .webPageEmpty: return nil } } + +public class WebpagePreviewMessageAttribute: MessageAttribute, Equatable { + public let associatedPeerIds: [PeerId] = [] + public let associatedMediaIds: [MediaId] = [] + + public init() { + } + + required public init(decoder: PostboxDecoder) { + } + + public func encode(_ encoder: PostboxEncoder) { + } + + public static func ==(lhs: WebpagePreviewMessageAttribute, rhs: WebpagePreviewMessageAttribute) -> Bool { + return true + } +} diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index e7b968033a..fbce4bce49 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -989,7 +989,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: if previousState.pts >= pts { } else if previousState.pts + ptsCount == pts { switch apiWebpage { - case let .webPageEmpty(id): + case let .webPageEmpty(flags, id, url): + let _ = flags + let _ = url updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { @@ -1204,7 +1206,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } case let .updateWebPage(apiWebpage, _, _): switch apiWebpage { - case let .webPageEmpty(id): + case let .webPageEmpty(flags, id, url): + let _ = flags + let _ = url updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { @@ -1523,11 +1527,12 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: switch draft { case .draftMessageEmpty: inputState = nil - case let .draftMessage(_, replyToMsgHeader, message, entities, date): + case let .draftMessage(_, replyToMsgHeader, message, entities, media, date): + let _ = media var replySubject: EngineMessageReplySubject? if let replyToMsgHeader = replyToMsgHeader { switch replyToMsgHeader { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyToTopId, quoteText, quoteEntities): + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities): let _ = replyHeader let _ = replyToTopId @@ -2959,7 +2964,9 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views) case let .updateChannelWebPage(_, apiWebpage, _, _): switch apiWebpage { - case let .webPageEmpty(id): + case let .webPageEmpty(flags, id, url): + let _ = flags + let _ = url updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift index 103078dda9..f4aed3660b 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift @@ -206,7 +206,7 @@ private func synchronizeChatInputState(transaction: Transaction, postbox: Postbo replyTo = .inputReplyToMessage(flags: innerFlags, replyToMsgId: topMsgId, topMsgId: topMsgId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil) } - return network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: inputState?.text ?? "", entities: apiEntitiesFromMessageTextEntities(inputState?.entities ?? [], associatedPeers: SimpleDictionary()))) + return network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: inputState?.text ?? "", entities: apiEntitiesFromMessageTextEntities(inputState?.entities ?? [], associatedPeers: SimpleDictionary()), media: nil)) |> delay(2.0, queue: Queue.concurrentDefaultQueue()) |> `catch` { _ -> Signal in return .single(.boolFalse) diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 0974d8e646..93268aac8e 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -57,7 +57,8 @@ extension Api.MessageMedia { return collectPreCachedResources(for: document) } return nil - case let .messageMediaWebPage(webPage): + case let .messageMediaWebPage(flags, webPage): + let _ = flags var result: [(MediaResource, Data)]? switch webPage { case let .webPage(_, _, _, _, _, _, _, _, _, photo, _, _, _, _, _, _, document, _, _): diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 90ad9f1efc..7bf8020534 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -110,6 +110,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case setChatWallpaper(wallpaper: TelegramWallpaper) case setSameChatWallpaper(wallpaper: TelegramWallpaper) case giftCode(slug: String, fromGiveaway: Bool, boostPeerId: PeerId?, months: Int32) + case giveawayLaunched public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -206,6 +207,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) }) case 36: self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), boostPeerId: PeerId(decoder.decodeInt64ForKey("pi", orElse: 0)), months: decoder.decodeInt32ForKey("months", orElse: 0)) + case 37: + self = .giveawayLaunched default: self = .unknown } @@ -395,6 +398,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeNil(forKey: "pi") } encoder.encodeInt32(months, forKey: "months") + case .giveawayLaunched: + encoder.encodeInt32(37, forKey: "_rawValue") } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift index f56add72b1..b878d1c729 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift @@ -69,6 +69,34 @@ public final class TelegraMediaWebpageThemeAttribute: PostboxCoding, Equatable { } } +public struct TelegramMediaWebpageDisplayOptions: Codable, Equatable { + public enum CodingKeys: String, CodingKey { + case position = "p" + case largeMedia = "lm" + } + + public enum Position: Int32, Codable { + case aboveText = 0 + case belowText = 1 + } + + public var position: Position? + public var largeMedia: Bool? + + public static let `default` = TelegramMediaWebpageDisplayOptions( + position: nil, + largeMedia: nil + ) + + public init( + position: Position?, + largeMedia: Bool? + ) { + self.position = position + self.largeMedia = largeMedia + } +} + public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public let url: String public let displayUrl: String @@ -89,7 +117,28 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public let attributes: [TelegramMediaWebpageAttribute] public let instantPage: InstantPage? - public init(url: String, displayUrl: String, hash: Int32, type: String?, websiteName: String?, title: String?, text: String?, embedUrl: String?, embedType: String?, embedSize: PixelDimensions?, duration: Int?, author: String?, image: TelegramMediaImage?, file: TelegramMediaFile?, story: TelegramMediaStory?, attributes: [TelegramMediaWebpageAttribute], instantPage: InstantPage?) { + public let displayOptions: TelegramMediaWebpageDisplayOptions + + public init( + url: String, + displayUrl: String, + hash: Int32, + type: String?, + websiteName: String?, + title: String?, + text: String?, + embedUrl: String?, + embedType: String?, + embedSize: PixelDimensions?, + duration: Int?, + author: String?, + image: TelegramMediaImage?, + file: TelegramMediaFile?, + story: TelegramMediaStory?, + attributes: [TelegramMediaWebpageAttribute], + instantPage: InstantPage?, + displayOptions: TelegramMediaWebpageDisplayOptions + ) { self.url = url self.displayUrl = displayUrl self.hash = hash @@ -107,6 +156,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { self.story = story self.attributes = attributes self.instantPage = instantPage + self.displayOptions = displayOptions } public init(decoder: PostboxDecoder) { @@ -163,6 +213,8 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { } else { self.instantPage = nil } + + self.displayOptions = decoder.decodeCodable(TelegramMediaWebpageDisplayOptions.self, forKey: "do") ?? TelegramMediaWebpageDisplayOptions.default } public func encode(_ encoder: PostboxEncoder) { @@ -239,6 +291,31 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "ip") } + + encoder.encodeCodable(self.displayOptions, forKey: "do") + } + + public func withDisplayOptions(_ displayOptions: TelegramMediaWebpageDisplayOptions) -> TelegramMediaWebpageLoadedContent { + return TelegramMediaWebpageLoadedContent( + url: self.url, + displayUrl: self.displayUrl, + hash: self.hash, + type: self.type, + websiteName: self.websiteName, + title: self.title, + text: self.text, + embedUrl: self.embedUrl, + embedType: self.embedType, + embedSize: self.embedSize, + duration: self.duration, + author: self.author, + image: self.image, + file: self.file, + story: self.story, + attributes: self.attributes, + instantPage: self.instantPage, + displayOptions: displayOptions + ) } } @@ -296,6 +373,10 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage return false } + if lhs.displayOptions != rhs.displayOptions { + return false + } + return true } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift index 26b109bc37..f5f8dab374 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift @@ -51,7 +51,7 @@ func _internal_clearCloudDraftsInteractively(postbox: Postbox, network: Network, innerFlags |= 1 << 0 replyTo = .inputReplyToMessage(flags: innerFlags, replyToMsgId: 0, topMsgId: topMsgId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil) } - signals.append(network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: "", entities: nil)) + signals.append(network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: "", entities: nil, media: nil)) |> `catch` { _ -> Signal in return .single(.boolFalse) } diff --git a/submodules/TelegramCore/Sources/Utils/Log.swift b/submodules/TelegramCore/Sources/Utils/Log.swift index 3d0410a786..4278b78c02 100644 --- a/submodules/TelegramCore/Sources/Utils/Log.swift +++ b/submodules/TelegramCore/Sources/Utils/Log.swift @@ -108,6 +108,8 @@ public final class Logger { setPostboxLogger({ s in Logger.shared.log("Postbox", s) Logger.shared.shortLog("Postbox", s) + }, sync: { + Logger.shared.sync() }) } @@ -128,6 +130,14 @@ public final class Logger { self.basePath = basePath } + public func sync() { + self.queue.sync { + if let (currentFile, _) = self.file { + let _ = currentFile.sync() + } + } + } + public func collectLogs(prefix: String? = nil) -> Signal<[(String, String)], NoError> { return Signal { subscriber in self.queue.async { diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index 6f31de4e6c..b13bcf9c7d 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -47,7 +47,8 @@ public func webpagePreviewWithProgress(account: Account, url: String, webpageId: } } switch result { - case let .messageMediaWebPage(webpage): + case let .messageMediaWebPage(flags, webpage): + let _ = flags if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) { if case .Loaded = media.content { return .single(.result(media)) @@ -92,7 +93,7 @@ public func actualizedWebpage(account: Account, webpage: TelegramMediaWebpage) - if let updatedWebpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { return .single(updatedWebpage) } else if case let .webPageNotModified(_, viewsValue) = apiWebpage, let views = viewsValue, case let .Loaded(content) = webpage.content { - let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(url: content.url, displayUrl: content.displayUrl, hash: content.hash, type: content.type, websiteName: content.websiteName, title: content.title, text: content.text, embedUrl: content.embedUrl, embedType: content.embedType, embedSize: content.embedSize, duration: content.duration, author: content.author, image: content.image, file: content.file, story: content.story, attributes: content.attributes, instantPage: content.instantPage.flatMap({ InstantPage(blocks: $0.blocks, media: $0.media, isComplete: $0.isComplete, rtl: $0.rtl, url: $0.url, views: views) }))) + let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(url: content.url, displayUrl: content.displayUrl, hash: content.hash, type: content.type, websiteName: content.websiteName, title: content.title, text: content.text, embedUrl: content.embedUrl, embedType: content.embedType, embedSize: content.embedSize, duration: content.duration, author: content.author, image: content.image, file: content.file, story: content.story, attributes: content.attributes, instantPage: content.instantPage.flatMap({ InstantPage(blocks: $0.blocks, media: $0.media, isComplete: $0.isComplete, rtl: $0.rtl, url: $0.url, views: views) }), displayOptions: .default)) let updatedWebpage = TelegramMediaWebpage(webpageId: webpage.webpageId, content: updatedContent) updateMessageMedia(transaction: transaction, id: webpage.webpageId, media: updatedWebpage) return .single(updatedWebpage) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index ee3e0d9fb4..6e0823ad61 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -900,6 +900,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } case .giftCode: attributedString = NSAttributedString(string: strings.Notification_GiftLink, font: titleFont, textColor: primaryTextColor) + case .giveawayLaunched: + let resultTitleString = strings.Notification_GiveawayStarted(compactAuthorName) + attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) case .unknown: attributedString = nil } @@ -922,10 +925,6 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, resultTitleString = strings.Conversation_StoryExpiredMentionTextOutgoing(compactPeerName) } attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) - } else if let _ = media as? TelegramMediaGiveaway { - let compactAuthorName = message.author?.compactDisplayTitle ?? "" - let resultTitleString = strings.Notification_GiveawayStarted(compactAuthorName) - attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) } } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index d26f9f182d..cb90bdf215 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -361,6 +361,52 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode", "//submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode", "//submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode", + "//submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode", + "//submodules/TelegramUI/Components/WallpaperPreviewMedia", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + "//submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode", + "//submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode", + "//submodules/TelegramUI/Components/Chat/ChatRecentActionsController", + "//submodules/TelegramUI/Components/Chat/ChatNavigationButton", + "//submodules/TelegramUI/Components/Chat/ChatLoadingNode", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD new file mode 100644 index 0000000000..d3250e2fad --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInstantVideoMessageDurationNode", + module_name = "ChatInstantVideoMessageDurationNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/MediaPlayer:UniversalMediaPlayer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift b/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/Sources/ChatInstantVideoMessageDurationNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift rename to submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/Sources/ChatInstantVideoMessageDurationNode.swift index 64e726819e..e75e529677 100644 --- a/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode/Sources/ChatInstantVideoMessageDurationNode.swift @@ -46,10 +46,10 @@ private final class ChatInstantVideoMessageDurationNodeParameters: NSObject { } } -final class ChatInstantVideoMessageDurationNode: ASImageNode { +public final class ChatInstantVideoMessageDurationNode: ASImageNode { private var textColor: UIColor - var defaultDuration: Double? { + public var defaultDuration: Double? { didSet { if self.defaultDuration != oldValue { self.updateTimestamp() @@ -58,7 +58,7 @@ final class ChatInstantVideoMessageDurationNode: ASImageNode { } } - var isSeen: Bool = false { + public var isSeen: Bool = false { didSet { if self.isSeen != oldValue { self.updateContents() @@ -92,7 +92,7 @@ final class ChatInstantVideoMessageDurationNode: ASImageNode { private var statusDisposable: Disposable? private var statusValuePromise = Promise() - var status: Signal? { + public var status: Signal? { didSet { if let status = self.status { self.statusValuePromise.set(status) @@ -102,10 +102,10 @@ final class ChatInstantVideoMessageDurationNode: ASImageNode { } } - var size: CGSize = CGSize() - var sizeUpdated: ((CGSize) -> Void)? + public var size: CGSize = CGSize() + public var sizeUpdated: ((CGSize) -> Void)? - init(textColor: UIColor) { + public init(textColor: UIColor) { self.textColor = textColor super.init() @@ -127,7 +127,7 @@ final class ChatInstantVideoMessageDurationNode: ASImageNode { self.updateTimer?.invalidate() } - func updateTheme(textColor: UIColor) { + public func updateTheme(textColor: UIColor) { if !self.textColor.isEqual(textColor) { self.textColor = textColor self.updateContents() @@ -149,7 +149,7 @@ final class ChatInstantVideoMessageDurationNode: ASImageNode { self.updateTimer = nil } - func updateTimestamp() { + public func updateTimestamp() { if let statusValue = self.statusValue, Double(0.0).isLess(than: statusValue.duration) { let timestampSeconds: Double if !statusValue.generationTimestamp.isZero { diff --git a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD new file mode 100644 index 0000000000..d3a63d832d --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatLoadingNode", + module_name = "ChatLoadingNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/ActivityIndicator", + "//submodules/WallpaperBackgroundNode", + "//submodules/ShimmerEffect", + "//submodules/ChatPresentationInterfaceState", + "//submodules/AccountContext", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift similarity index 88% rename from submodules/TelegramUI/Sources/ChatLoadingNode.swift rename to submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift index b194b51613..760609e3c5 100644 --- a/submodules/TelegramUI/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift @@ -10,13 +10,20 @@ import WallpaperBackgroundNode import ShimmerEffect import ChatPresentationInterfaceState import AccountContext +import ChatMessageItem +import ChatMessageItemView +import ChatMessageStickerItemNode +import ChatMessageInstantVideoItemNode +import ChatMessageAnimatedStickerItemNode +import ChatMessageBubbleItemNode +import ChatMessageItemImpl -final class ChatLoadingNode: ASDisplayNode { +public final class ChatLoadingNode: ASDisplayNode { private let backgroundNode: NavigationBackgroundNode private let activityIndicator: ActivityIndicator private let offset: CGPoint - init(context: AccountContext, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners) { + public init(context: AccountContext, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners) { self.backgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: theme, wallpaper: chatWallpaper), enableBlur: context.sharedContext.energyUsageSettings.fullTranslucency && dateFillNeedsBlur(theme: theme, wallpaper: chatWallpaper)) let serviceColor = serviceMessageColorComponents(theme: theme, wallpaper: chatWallpaper) @@ -33,7 +40,7 @@ final class ChatLoadingNode: ASDisplayNode { self.addSubnode(self.activityIndicator) } - func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) let backgroundSize: CGFloat = 30.0 @@ -44,7 +51,7 @@ final class ChatLoadingNode: ASDisplayNode { transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: displayRect.minX + floor((displayRect.width - activitySize.width) / 2.0) + self.offset.x, y: displayRect.minY + floor((displayRect.height - activitySize.height) / 2.0) + self.offset.y), size: activitySize)) } - var progressFrame: CGRect { + public var progressFrame: CGRect { return self.backgroundNode.frame } } @@ -53,22 +60,22 @@ private let avatarSize = CGSize(width: 38.0, height: 38.0) private let avatarImage = generateFilledCircleImage(diameter: avatarSize.width, color: .white) private let avatarBorderImage = generateCircleImage(diameter: avatarSize.width, lineWidth: 1.0 - UIScreenPixel, color: .white) -final class ChatLoadingPlaceholderMessageContainer { - var avatarNode: ASImageNode? - var avatarBorderNode: ASImageNode? +public final class ChatLoadingPlaceholderMessageContainer { + public var avatarNode: ASImageNode? + public var avatarBorderNode: ASImageNode? - let bubbleNode: ASImageNode - let bubbleBorderNode: ASImageNode + public let bubbleNode: ASImageNode + public let bubbleBorderNode: ASImageNode - var parentView: UIView? { + public var parentView: UIView? { return self.bubbleNode.supernode?.view } - var frame: CGRect { + public var frame: CGRect { return self.bubbleNode.frame } - init(bubbleImage: UIImage?, bubbleBorderImage: UIImage?) { + public init(bubbleImage: UIImage?, bubbleBorderImage: UIImage?) { self.bubbleNode = ASImageNode() self.bubbleNode.displaysAsynchronously = false self.bubbleNode.image = bubbleImage @@ -78,12 +85,12 @@ final class ChatLoadingPlaceholderMessageContainer { self.bubbleBorderNode.image = bubbleBorderImage } - func setup(maskNode: ASDisplayNode, borderMaskNode: ASDisplayNode) { + public func setup(maskNode: ASDisplayNode, borderMaskNode: ASDisplayNode) { maskNode.addSubnode(self.bubbleNode) borderMaskNode.addSubnode(self.bubbleBorderNode) } - func animateWith(_ listItemNode: ListViewItemNode, delay: Double, transition: ContainedViewLayoutTransition) { + public func animateWith(_ listItemNode: ListViewItemNode, delay: Double, transition: ContainedViewLayoutTransition) { listItemNode.allowsGroupOpacity = true listItemNode.alpha = 1.0 listItemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay, completion: { _ in @@ -91,17 +98,17 @@ final class ChatLoadingPlaceholderMessageContainer { }) if let bubbleItemNode = listItemNode as? ChatMessageBubbleItemNode { - bubbleItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) + bubbleItemNode.animateFromLoadingPlaceholder(delay: delay, transition: transition) } else if let stickerItemNode = listItemNode as? ChatMessageStickerItemNode { - stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) + stickerItemNode.animateFromLoadingPlaceholder(delay: delay, transition: transition) } else if let stickerItemNode = listItemNode as? ChatMessageAnimatedStickerItemNode { - stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) + stickerItemNode.animateFromLoadingPlaceholder(delay: delay, transition: transition) } else if let videoItemNode = listItemNode as? ChatMessageInstantVideoItemNode { - videoItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) + videoItemNode.animateFromLoadingPlaceholder(delay: delay, transition: transition) } } - func update(size: CGSize, hasAvatar: Bool, rect: CGRect, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, hasAvatar: Bool, rect: CGRect, transition: ContainedViewLayoutTransition) { var avatarOffset: CGFloat = 0.0 if hasAvatar && self.avatarNode == nil { @@ -133,7 +140,7 @@ final class ChatLoadingPlaceholderMessageContainer { } } -final class ChatLoadingPlaceholderNode: ASDisplayNode { +public final class ChatLoadingPlaceholderNode: ASDisplayNode { private weak var backgroundNode: WallpaperBackgroundNode? private let context: AccountContext @@ -155,7 +162,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { private var validLayout: (CGSize, UIEdgeInsets, LayoutMetrics)? - init(context: AccountContext, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, backgroundNode: WallpaperBackgroundNode) { + public init(context: AccountContext, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, backgroundNode: WallpaperBackgroundNode) { self.context = context self.backgroundNode = backgroundNode @@ -201,7 +208,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } } - override func didLoad() { + override public func didLoad() { super.didLoad() self.containerNode.view.mask = self.maskNode.view @@ -217,10 +224,8 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } private var bottomInset: (Int, CGFloat)? - func setup(_ historyNode: ChatHistoryNode, updating: Bool = false) { - guard let listNode = historyNode as? ListView else { - return - } + public func setup(_ historyNode: ListView, updating: Bool = false) { + let listNode = historyNode var listItemNodes: [ASDisplayNode] = [] var count = 0 @@ -285,10 +290,11 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } private var didAnimateOut = false - func animateOut(_ historyNode: ChatHistoryNode, completion: @escaping () -> Void = {}) { - guard let listNode = historyNode as? ListView, let (size, _, _) = self.validLayout else { + public func animateOut(_ historyNode: ListView, completion: @escaping () -> Void = {}) { + guard let (size, _, _) = self.validLayout else { return } + let listNode = historyNode self.didAnimateOut = true self.backgroundNode?.updateIsLooping(false) @@ -372,7 +378,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } } - func addContentOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { + public func addContentOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { self.maskNode.bounds = self.maskNode.bounds.offsetBy(dx: 0.0, dy: -offset) self.borderMaskNode.bounds = self.borderMaskNode.bounds.offsetBy(dx: 0.0, dy: -offset) transition.animateOffsetAdditive(node: self.maskNode, offset: offset) @@ -382,7 +388,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } } - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame @@ -392,14 +398,14 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } } - enum ChatType: Equatable { + public enum ChatType: Equatable { case generic case user case group case channel } private var chatType: ChatType = .channel - func updatePresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) { + public func updatePresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) { var chatType: ChatType = .channel if let peer = chatPresentationInterfaceState.renderedPeer?.peer { if peer is TelegramUser { @@ -423,7 +429,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } } - func updateLayout(size: CGSize, insets: UIEdgeInsets, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, insets: UIEdgeInsets, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition) { self.validLayout = (size, insets, metrics) let bounds = CGRect(origin: .zero, size: size) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD new file mode 100644 index 0000000000..e487f7b72c --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD @@ -0,0 +1,38 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageActionBubbleContentNode", + module_name = "ChatMessageActionBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/LocalizedPeerData", + "//submodules/UrlEscaping", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramUniversalVideoContent", + "//submodules/GalleryUI", + "//submodules/WallpaperBackgroundNode", + "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift similarity index 93% rename from submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index 1eac4b7c6b..ec2ffe32c4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -26,13 +26,13 @@ private func attributedServiceMessageString(theme: ChatPresentationThemeData, st return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false, forForumOverview: forForumOverview) } -class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { - let labelNode: TextNodeWithEntities +public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { + public let labelNode: TextNodeWithEntities private var dustNode: InvisibleInkDustNode? - var backgroundNode: WallpaperBubbleBackgroundNode? - var backgroundColorNode: ASDisplayNode - let backgroundMaskNode: ASImageNode - var linkHighlightingNode: LinkHighlightingNode? + public var backgroundNode: WallpaperBubbleBackgroundNode? + public var backgroundColorNode: ASDisplayNode + public let backgroundMaskNode: ASImageNode + public var linkHighlightingNode: LinkHighlightingNode? private let mediaBackgroundNode: ASImageNode fileprivate var imageNode: TransformImageNode? @@ -46,7 +46,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { private var cachedMaskBackgroundImage: (CGPoint, UIImage, [CGRect])? private var absoluteRect: (CGRect, CGSize)? - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { if oldValue != self.visibility { switch self.visibility { @@ -64,7 +64,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - required init() { + required public init() { self.labelNode = TextNodeWithEntities() self.labelNode.textNode.isUserInteractionEnabled = false self.labelNode.textNode.displaysAsynchronously = false @@ -81,7 +81,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.labelNode.textNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -89,11 +89,11 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { self.fetchDisposable.dispose() } - override func didLoad() { + override public func didLoad() { super.didLoad() } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let imageNode = self.imageNode, self.item?.message.id == messageId { return (imageNode, imageNode.bounds, { [weak self] in guard let strongSelf = self, let imageNode = strongSelf.imageNode else { @@ -122,7 +122,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { var mediaHidden = false var currentMedia: Media? if let item = item { @@ -152,18 +152,13 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { return mediaHidden } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode) let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage return { item, layoutConstants, _, _, _, _ in - var isGiveaway = false - if let _ = item.message.media.first(where: { $0 is TelegramMediaGiveaway }) { - isGiveaway = true - } - - let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: isGiveaway ? .none : .center, isDetached: isGiveaway) + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) let backgroundImage = PresentationResourcesChat.chatActionPhotoBackgroundImage(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) @@ -235,8 +230,6 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if let _ = image { backgroundSize.height += imageSize.height + 10 - } else if isGiveaway { - backgroundSize.height += 8.0 } return (backgroundSize.width, { boundingWidth in @@ -440,7 +433,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if let backgroundNode = self.backgroundNode { @@ -451,19 +444,19 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let backgroundNode = self.backgroundNode { backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } } - override func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + override public func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { if let backgroundNode = self.backgroundNode { backgroundNode.offsetSpring(value: value, duration: duration, damping: damping) } } - override func updateTouchesAtPoint(_ point: CGPoint?) { + override public func updateTouchesAtPoint(_ point: CGPoint?) { if let item = self.item { var rects: [(CGRect, CGRect)]? let textNodeFrame = self.labelNode.textNode.frame @@ -516,11 +509,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { - if let item = self.item, item.message.media.first(where: { $0 is TelegramMediaGiveaway }) != nil { - return .none - } - + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.labelNode.textNode.frame if let (index, attributes) = self.labelNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { @@ -539,7 +528,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { return .hashtag(hashtag.peerName, hashtag.hashtag) } } - if let imageNode = self.imageNode, imageNode.frame.contains(point) { + if let imageNode = imageNode, imageNode.frame.contains(point) { return .openMessage } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD new file mode 100644 index 0000000000..43f3ea0a24 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageActionButtonsNode", + module_name = "ChatMessageActionButtonsNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/WallpaperBackgroundNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift similarity index 88% rename from submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index bbbda3cf3b..08017ba773 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -10,6 +10,56 @@ import WallpaperBackgroundNode private let titleFont = Font.medium(16.0) +private extension UIBezierPath { + convenience init(roundRect rect: CGRect, topLeftRadius: CGFloat = 0.0, topRightRadius: CGFloat = 0.0, bottomLeftRadius: CGFloat = 0.0, bottomRightRadius: CGFloat = 0.0) { + self.init() + + let path = CGMutablePath() + + let topLeft = rect.origin + let topRight = CGPoint(x: rect.maxX, y: rect.minY) + let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY) + let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY) + + if topLeftRadius != .zero { + path.move(to: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y)) + } else { + path.move(to: CGPoint(x: topLeft.x, y: topLeft.y)) + } + + if topRightRadius != .zero { + path.addLine(to: CGPoint(x: topRight.x-topRightRadius, y: topRight.y)) + path.addCurve(to: CGPoint(x: topRight.x, y: topRight.y+topRightRadius), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y + topRightRadius)) + } else { + path.addLine(to: CGPoint(x: topRight.x, y: topRight.y)) + } + + if bottomRightRadius != .zero { + path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius)) + path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius, y: bottomRight.y)) + } else { + path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y)) + } + + if bottomLeftRadius != .zero { + path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius, y: bottomLeft.y)) + path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius)) + } else { + path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y)) + } + + if topLeftRadius != .zero { + path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius)) + path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y)) + } else { + path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y)) + } + + path.closeSubpath() + self.cgPath = path + } +} + private final class ChatMessageActionButtonNode: ASDisplayNode { //private let backgroundBlurNode: NavigationBackgroundNode private var backgroundBlurView: PortalView? @@ -293,7 +343,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size) titleNode.layer.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - animation.animator.updatePosition(layer: titleNode.layer, position: titleFrame.center, completion: nil) + animation.animator.updatePosition(layer: titleNode.layer, position: CGPoint(x: titleFrame.midX, y: titleFrame.midY), completion: nil) if let buttonView = node.buttonView { buttonView.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)) @@ -316,17 +366,17 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } -final class ChatMessageActionButtonsNode: ASDisplayNode { +public final class ChatMessageActionButtonsNode: ASDisplayNode { private var buttonNodes: [ChatMessageActionButtonNode] = [] private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)? private var buttonLongTappedWrapper: ((ReplyMarkupButton) -> Void)? - var buttonPressed: ((ReplyMarkupButton) -> Void)? - var buttonLongTapped: ((ReplyMarkupButton) -> Void)? + public var buttonPressed: ((ReplyMarkupButton) -> Void)? + public var buttonLongTapped: ((ReplyMarkupButton) -> Void)? private var absolutePosition: (CGRect, CGSize)? - override init() { + override public init() { super.init() self.buttonPressedWrapper = { [weak self] button in @@ -342,7 +392,7 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { } } - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) for button in buttonNodes { @@ -353,7 +403,7 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? [] return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, message, constrainedWidth in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD new file mode 100644 index 0000000000..4d00881529 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD @@ -0,0 +1,60 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageAnimatedStickerItemNode", + module_name = "ChatMessageAnimatedStickerItemNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/MediaResources", + "//submodules/StickerResources", + "//submodules/ContextUI", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/Emoji", + "//submodules/Markdown", + "//submodules/ManagedAnimationNode", + "//submodules/SlotMachineAnimationNode", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/ShimmerEffect", + "//submodules/WallpaperBackgroundNode", + "//submodules/LocalMediaResources", + "//submodules/AppBundle", + "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode", + "//submodules/TelegramUI/Components/Chat/MessageHaptics", + "//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift similarity index 92% rename from submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index bc674a22d3..e8ca295570 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -33,12 +33,25 @@ import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageBubbleContentNode import ChatMessageReplyInfoNode +import ChatMessageItem +import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageThreadInfoNode +import ChatMessageActionButtonsNode +import ChatSwipeToReplyRecognizer +import ChatMessageReactionsFooterContentNode +import ManagedDiceAnimationNode +import MessageHaptics +import ChatMessageTransitionNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont -protocol GenericAnimatedStickerNode: ASDisplayNode { +public protocol GenericAnimatedStickerNode: ASDisplayNode { func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) var currentFrameIndex: Int { get } @@ -46,197 +59,32 @@ protocol GenericAnimatedStickerNode: ASDisplayNode { } extension DefaultAnimatedStickerNodeImpl: GenericAnimatedStickerNode { - func setFrameIndex(_ frameIndex: Int) { + public func setFrameIndex(_ frameIndex: Int) { self.stop() self.play(fromIndex: frameIndex) } } extension SlotMachineAnimationNode: GenericAnimatedStickerNode { - var currentFrameIndex: Int { + public var currentFrameIndex: Int { return 0 } - func setFrameIndex(_ frameIndex: Int) { + public func setFrameIndex(_ frameIndex: Int) { } } -class ChatMessageShareButton: HighlightableButtonNode { - private var backgroundContent: WallpaperBubbleBackgroundNode? - //private let backgroundNode: NavigationBackgroundNode - private var backgroundBlurView: PortalView? - - private let iconNode: ASImageNode - private var iconOffset = CGPoint() - - private var theme: PresentationTheme? - private var isReplies: Bool = false - - private var textNode: ImmediateTextNode? - - private var absolutePosition: (CGRect, CGSize)? - - init() { - //self.backgroundNode = NavigationBackgroundNode(color: .clear) - self.iconNode = ASImageNode() - - super.init(pointerStyle: nil) - - self.allowsGroupOpacity = true - - //self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { - var isReplies = false - var replyCount = 0 - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - for attribute in message.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute { - replyCount = Int(attribute.count) - isReplies = true - break - } - } - } - if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id { - replyCount = 0 - isReplies = false - } - if disableComments { - replyCount = 0 - isReplies = false - } - - if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { - self.theme = presentationData.theme.theme - self.isReplies = isReplies - - var updatedIconImage: UIImage? - var updatedIconOffset = CGPoint() - if case .pinnedMessages = subject { - updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) - } else if isReplies { - updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { - updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) - } else { - updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - } - //self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate) - self.iconNode.image = updatedIconImage - self.iconOffset = updatedIconOffset - } - var size = CGSize(width: 30.0, height: 30.0) - var offsetIcon = false - if isReplies, replyCount > 0 { - offsetIcon = true - - let textNode: ImmediateTextNode - if let current = self.textNode { - textNode = current - } else { - textNode = ImmediateTextNode() - self.textNode = textNode - self.addSubnode(textNode) - } - - let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) - - let countString: String - if replyCount >= 1000 * 1000 { - countString = "\(replyCount / 1000_000)M" - } else if replyCount >= 1000 { - countString = "\(replyCount / 1000)K" - } else { - countString = "\(replyCount)" - } - - textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) - let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) - size.height += textSize.height - 1.0 - textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) - } else if let textNode = self.textNode { - self.textNode = nil - textNode.removeFromSupernode() - } - - if self.backgroundBlurView == nil { - if let backgroundBlurView = controllerInteraction.presentationContext.backgroundNode?.makeFreeBackground() { - self.backgroundBlurView = backgroundBlurView - self.view.insertSubview(backgroundBlurView.view, at: 0) - - backgroundBlurView.view.clipsToBounds = true - } - } - if let backgroundBlurView = self.backgroundBlurView { - backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size) - backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0 - } - - //self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) - //self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0, transition: .immediate) - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.iconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.iconOffset.y), size: image.size) - } - - - if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { - if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - backgroundContent.clipsToBounds = true - self.backgroundContent = backgroundContent - self.insertSubnode(backgroundContent, at: 0) - } - } else { - self.backgroundContent?.removeFromSupernode() - self.backgroundContent = nil - } - - if let backgroundContent = self.backgroundContent { - //self.backgroundNode.isHidden = true - self.backgroundBlurView?.view.isHidden = true - backgroundContent.cornerRadius = min(size.width, size.height) / 2.0 - backgroundContent.frame = CGRect(origin: CGPoint(), size: size) - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } else { - //self.backgroundNode.isHidden = false - self.backgroundBlurView?.view.isHidden = false - } - - return size - } - - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { - self.absolutePosition = (rect, containerSize) - if let backgroundContent = self.backgroundContent { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } +extension ManagedDiceAnimationNode: GenericAnimatedStickerNode { } -class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { - let contextSourceNode: ContextExtractedContentContainingNode +public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { + public let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode - let imageNode: TransformImageNode + public let imageNode: TransformImageNode private var enableSynchronousImageApply: Bool = false private var backgroundNode: WallpaperBubbleBackgroundNode? - private(set) var placeholderNode: StickerShimmerEffectNode - private(set) var animationNode: GenericAnimatedStickerNode? + public private(set) var placeholderNode: StickerShimmerEffectNode + public private(set) var animationNode: GenericAnimatedStickerNode? private var animationSize: CGSize? private var didSetUpAnimationNode = false private var isPlaying = false @@ -244,7 +92,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private let textNode: TextNodeWithEntities private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = [] - private var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode? private var enqueuedAdditionalAnimations: [(Int, Double)] = [] private var additionalAnimationsCommitTimer: SwiftSignalKit.Timer? @@ -255,10 +102,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var deliveryFailedNode: ChatMessageDeliveryFailedNode? private var shareButtonNode: ChatMessageShareButton? - var telegramFile: TelegramMediaFile? - var emojiFile: TelegramMediaFile? - var telegramDice: TelegramMediaDice? - var emojiString: String? + public var telegramFile: TelegramMediaFile? + public var emojiFile: TelegramMediaFile? + public var telegramDice: TelegramMediaDice? + public var emojiString: String? private let disposable = MetaDisposable() private let disposables = DisposableSet() @@ -294,7 +141,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var wasPending: Bool = false private var didChangeFromPendingToSent: Bool = false - required init() { + required public init() { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() self.imageNode = TransformImageNode() @@ -391,7 +238,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.additionalAnimationsCommitTimer?.invalidate() } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -406,7 +253,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -483,7 +330,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.view.addGestureRecognizer(replyRecognizer) } - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none let isVisible = self.visibility != .none @@ -583,7 +430,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { + override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { super.setupItem(item, synchronousLoad: synchronousLoad) if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal { @@ -745,13 +592,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let isPlaying = self.visibilityStatus == true && !self.forceStopAnimations if !isPlaying { self.removeAdditionalAnimations() - - if let overlayMeshAnimationNode = self.overlayMeshAnimationNode { - self.overlayMeshAnimationNode = nil - if let transitionNode = item.controllerInteraction.getMessageTransitionNode() as? ChatMessageTransitionNode { - transitionNode.remove(decorationNode: overlayMeshAnimationNode) - } - } } if let animationNode = self.animationNode as? AnimatedStickerNode { if self.isPlaying != isPlaying || (isPlaying && !self.didSetUpAnimationNode) { @@ -821,13 +661,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func updateStickerSettings(forceStopAnimations: Bool) { + override public func updateStickerSettings(forceStopAnimations: Bool) { self.forceStopAnimations = forceStopAnimations self.updateVisibility() } private var absoluteRect: (CGRect, CGSize)? - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if !self.contextSourceNode.isExtractedToContextPreview { var rect = rect @@ -881,7 +721,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let backgroundNode = self.backgroundNode { backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } @@ -891,7 +731,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { + override public func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { super.updateAccessibilityData(accessibilityData) self.messageAccessibilityArea.accessibilityLabel = accessibilityData.label @@ -922,7 +762,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { + override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { var displaySize = CGSize(width: 180.0, height: 180.0) let telegramFile = self.telegramFile let emojiFile = self.emojiFile @@ -1919,7 +1759,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { item.controllerInteraction.commitEmojiInteraction(item.message.id, item.message.text.strippedEmoji, EmojiInteraction(animations: animations), file) } - func playEmojiInteraction(_ interaction: EmojiInteraction) { + public func playEmojiInteraction(_ interaction: EmojiInteraction) { guard interaction.animations.count <= 7 else { return } @@ -1965,7 +1805,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - func playAdditionalEmojiAnimation(index: Int) { + public func playAdditionalEmojiAnimation(index: Int) { guard let item = self.item else { return } @@ -2004,7 +1844,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.playEffectAnimation(resource: effect.resource, isStickerEffect: true) } - func playEffectAnimation(resource: MediaResource, isStickerEffect: Bool = false) { + public func playEffectAnimation(resource: MediaResource, isStickerEffect: Bool = false) { guard let item = self.item else { return } @@ -2440,7 +2280,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } private var playedSwipeToReplyHaptic = false - @objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { + @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 var leftOffset: CGFloat = 0.0 var swipeOffset: CGFloat = 45.0 @@ -2564,7 +2404,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view } @@ -2579,7 +2419,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { return super.hitTest(point, with: event) } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { guard let item = self.item else { return } @@ -2661,7 +2501,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func updateHighlightedState(animated: Bool) { + override public func updateHighlightedState(animated: Bool) { super.updateHighlightedState(animated: animated) if let item = self.item { @@ -2686,11 +2526,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func cancelInsertionAnimations() { + override public func cancelInsertionAnimations() { self.layer.removeAllAnimations() } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -2708,27 +2548,41 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { super.animateRemoved(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { super.animateAdded(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { + override public func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { return self.contextSourceNode } - override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + override public func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + public final class AnimationTransitionTextInput { + public let backgroundView: UIView + public let contentView: UIView + public let sourceRect: CGRect + public let scrollOffset: CGFloat - func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) { + public init(backgroundView: UIView, contentView: UIView, sourceRect: CGRect, scrollOffset: CGFloat) { + self.backgroundView = backgroundView + self.contentView = contentView + self.sourceRect = sourceRect + self.scrollOffset = scrollOffset + } + } + + public func animateContentFromTextInputField(textInput: AnimationTransitionTextInput, transition: CombinedTransition) { guard let _ = self.item else { return } @@ -2787,8 +2641,56 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) } + + public final class AnimationTransitionSticker { + public let imageNode: TransformImageNode? + public let animationNode: ASDisplayNode? + public let placeholderNode: ASDisplayNode? + public let imageLayer: CALayer? + public let relativeSourceRect: CGRect + + var sourceFrame: CGRect { + if let imageNode = self.imageNode { + return imageNode.frame + } else if let imageLayer = self.imageLayer { + return imageLayer.bounds + } else { + return CGRect(origin: CGPoint(), size: relativeSourceRect.size) + } + } + + var sourceLayer: CALayer? { + if let imageNode = self.imageNode { + return imageNode.layer + } else if let imageLayer = self.imageLayer { + return imageLayer + } else { + return nil + } + } + + func snapshotContentTree() -> UIView? { + if let animationNode = self.animationNode { + return animationNode.view.snapshotContentTree() + } else if let imageNode = self.imageNode { + return imageNode.view.snapshotContentTree() + } else if let sourceLayer = self.imageLayer { + return sourceLayer.snapshotContentTreeAsView() + } else { + return nil + } + } + + public init(imageNode: TransformImageNode?, animationNode: ASDisplayNode?, placeholderNode: ASDisplayNode?, imageLayer: CALayer?, relativeSourceRect: CGRect) { + self.imageNode = imageNode + self.animationNode = animationNode + self.placeholderNode = placeholderNode + self.imageLayer = imageLayer + self.relativeSourceRect = relativeSourceRect + } + } - func animateContentFromStickerGridItem(stickerSource: ChatMessageTransitionNode.Sticker, transition: CombinedTransition) { + public func animateContentFromStickerGridItem(stickerSource: AnimationTransitionSticker, transition: CombinedTransition) { guard let _ = self.item else { return } @@ -2874,8 +2776,26 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { placeholderNode.layer.animateAlpha(from: 0.0, to: placeholderNode.alpha, duration: 0.4) } } + + public final class AnimationTransitionReplyPanel { + public let titleNode: ASDisplayNode + public let textNode: ASDisplayNode + public let lineNode: ASDisplayNode + public let imageNode: ASDisplayNode + public let relativeSourceRect: CGRect + public let relativeTargetRect: CGRect - func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) { + public init(titleNode: ASDisplayNode, textNode: ASDisplayNode, lineNode: ASDisplayNode, imageNode: ASDisplayNode, relativeSourceRect: CGRect, relativeTargetRect: CGRect) { + self.titleNode = titleNode + self.textNode = textNode + self.lineNode = lineNode + self.imageNode = imageNode + self.relativeSourceRect = relativeSourceRect + self.relativeTargetRect = relativeTargetRect + } + } + + public func animateReplyPanel(sourceReplyPanel: AnimationTransitionReplyPanel, transition: CombinedTransition) { if let replyInfoNode = self.replyInfoNode { let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) @@ -2895,7 +2815,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) { + public func animateFromLoadingPlaceholder(delay: Double, transition: ContainedViewLayoutTransition) { guard let item = self.item else { return } @@ -2905,14 +2825,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay) } - override func openMessageContextMenu() { + override public func openMessageContextMenu() { guard let item = self.item else { return } item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } - override func targetReactionView(value: MessageReaction.Reaction) -> UIView? { + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result } @@ -2922,7 +2842,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { return nil } - override func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: StoryId) -> UIView? { guard let item = self.item else { return nil } @@ -2938,17 +2858,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { return nil } - override func unreadMessageRangeUpdated() { + override public func unreadMessageRangeUpdated() { self.updateVisibility() } - override func contentFrame() -> CGRect { + override public func contentFrame() -> CGRect { return self.imageNode.frame } } -struct AnimatedEmojiSoundsConfiguration { - static var defaultValue: AnimatedEmojiSoundsConfiguration { +public struct AnimatedEmojiSoundsConfiguration { + public static var defaultValue: AnimatedEmojiSoundsConfiguration { return AnimatedEmojiSoundsConfiguration(sounds: [:]) } @@ -2958,7 +2878,7 @@ struct AnimatedEmojiSoundsConfiguration { self.sounds = sounds } - static func with(appConfiguration: AppConfiguration, account: Account) -> AnimatedEmojiSoundsConfiguration { + public static func with(appConfiguration: AppConfiguration, account: Account) -> AnimatedEmojiSoundsConfiguration { if let data = appConfiguration.data, let values = data["emojies_sounds"] as? [String: Any] { var sounds: [String: TelegramMediaFile] = [:] for (key, value) in values { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD new file mode 100644 index 0000000000..9bbaaa0da4 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageAttachedContentButtonNode", + module_name = "ChatMessageAttachedContentButtonNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ShimmerEffect", + ], + visibility = [ + "//visibility:public", + ], +) + diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift new file mode 100644 index 0000000000..7afd8d56c4 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift @@ -0,0 +1,235 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramPresentationData +import ChatPresentationInterfaceState +import ShimmerEffect + +private let buttonFont = Font.semibold(14.0) + +public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode { + private let textNode: TextNode + private let iconNode: ASImageNode + private let highlightedTextNode: TextNode + private let backgroundNode: ASImageNode + private let shimmerEffectNode: ShimmerEffectForegroundNode + + private var regularImage: UIImage? + private var highlightedImage: UIImage? + private var regularIconImage: UIImage? + private var highlightedIconImage: UIImage? + + public var pressed: (() -> Void)? + + private var titleColor: UIColor? + + public init() { + self.textNode = TextNode() + self.textNode.isUserInteractionEnabled = false + self.highlightedTextNode = TextNode() + self.highlightedTextNode.isUserInteractionEnabled = false + + self.shimmerEffectNode = ShimmerEffectForegroundNode() + self.shimmerEffectNode.cornerRadius = 5.0 + + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.displaysAsynchronously = false + + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displayWithoutProcessing = true + self.iconNode.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.shimmerEffectNode) + self.addSubnode(self.backgroundNode) + self.addSubnode(self.textNode) + self.addSubnode(self.highlightedTextNode) + self.highlightedTextNode.isHidden = true + + self.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.image = strongSelf.highlightedImage + strongSelf.iconNode.image = strongSelf.highlightedIconImage + strongSelf.textNode.isHidden = true + strongSelf.highlightedTextNode.isHidden = false + + let scale = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width + strongSelf.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false) + } else { + if let presentationLayer = strongSelf.layer.presentation() { + strongSelf.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false) + } + if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) { + strongSelf.view.addSubview(snapshot) + + snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshot.removeFromSuperview() + }) + } + + strongSelf.backgroundNode.image = strongSelf.regularImage + strongSelf.iconNode.image = strongSelf.regularIconImage + strongSelf.textNode.isHidden = false + strongSelf.highlightedTextNode.isHidden = true + } + } + } + + self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + @objc private func buttonPressed() { + self.pressed?() + } + + public func startShimmering() { + guard let titleColor = self.titleColor else { + return + } + self.shimmerEffectNode.isHidden = false + self.shimmerEffectNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + let backgroundFrame = self.backgroundNode.frame + self.shimmerEffectNode.frame = backgroundFrame + self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: backgroundFrame.size), within: backgroundFrame.size) + self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: titleColor.withAlphaComponent(0.3), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) + } + + public func stopShimmering() { + self.shimmerEffectNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in + self?.shimmerEffectNode.isHidden = true + }) + } + + public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> (_ width: CGFloat, _ regularImage: UIImage?, _ highlightedImage: UIImage?, _ iconImage: UIImage?, _ highlightedIconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ highlightedTitleColor: UIColor, _ inProgress: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode)) { + let previousRegularImage = current?.regularImage + let previousHighlightedImage = current?.highlightedImage + let previousRegularIconImage = current?.regularIconImage + let previousHighlightedIconImage = current?.highlightedIconImage + + let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout) + let maybeMakeHighlightedTextLayout = (current?.highlightedTextNode).flatMap(TextNode.asyncLayout) + + return { width, regularImage, highlightedImage, iconImage, highlightedIconImage, cornerIcon, title, titleColor, highlightedTitleColor, inProgress in + let targetNode: ChatMessageAttachedContentButtonNode + if let current = current { + targetNode = current + } else { + targetNode = ChatMessageAttachedContentButtonNode() + } + + let makeTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode) + if let maybeMakeTextLayout = maybeMakeTextLayout { + makeTextLayout = maybeMakeTextLayout + } else { + makeTextLayout = TextNode.asyncLayout(targetNode.textNode) + } + + let makeHighlightedTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode) + if let maybeMakeHighlightedTextLayout = maybeMakeHighlightedTextLayout { + makeHighlightedTextLayout = maybeMakeHighlightedTextLayout + } else { + makeHighlightedTextLayout = TextNode.asyncLayout(targetNode.highlightedTextNode) + } + + var updatedRegularImage: UIImage? + if regularImage !== previousRegularImage { + updatedRegularImage = regularImage + } + + var updatedHighlightedImage: UIImage? + if highlightedImage !== previousHighlightedImage { + updatedHighlightedImage = highlightedImage + } + + var updatedRegularIconImage: UIImage? + if iconImage !== previousRegularIconImage { + updatedRegularIconImage = iconImage + } + + var updatedHighlightedIconImage: UIImage? + if highlightedIconImage !== previousHighlightedIconImage { + updatedHighlightedIconImage = highlightedIconImage + } + + var iconWidth: CGFloat = 0.0 + if let iconImage = iconImage { + iconWidth = iconImage.size.width + 5.0 + } + + let labelInset: CGFloat = 8.0 + + let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) + + let (_, highlightedTextApply) = makeHighlightedTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: highlightedTitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) + + return (textSize.size.width + labelInset * 2.0, { refinedWidth, refinedHeight in + let size = CGSize(width: refinedWidth, height: refinedHeight) + return (size, { + targetNode.accessibilityLabel = title + + //targetNode.borderColor = UIColor.red.cgColor + //targetNode.borderWidth = 1.0 + + targetNode.titleColor = titleColor + + if let updatedRegularImage = updatedRegularImage { + targetNode.regularImage = updatedRegularImage + if !targetNode.textNode.isHidden { + targetNode.backgroundNode.image = updatedRegularImage + } + } + if let updatedHighlightedImage = updatedHighlightedImage { + targetNode.highlightedImage = updatedHighlightedImage + if targetNode.textNode.isHidden { + targetNode.backgroundNode.image = updatedHighlightedImage + } + } + if let updatedRegularIconImage = updatedRegularIconImage { + targetNode.regularIconImage = updatedRegularIconImage + if !targetNode.textNode.isHidden { + targetNode.iconNode.image = updatedRegularIconImage + } + } + if let updatedHighlightedIconImage = updatedHighlightedIconImage { + targetNode.highlightedIconImage = updatedHighlightedIconImage + if targetNode.iconNode.isHidden { + targetNode.iconNode.image = updatedHighlightedIconImage + } + } + + let _ = textApply() + let _ = highlightedTextApply() + + let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: size.height)) + var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) + targetNode.backgroundNode.frame = backgroundFrame + if let image = targetNode.iconNode.image { + if cornerIcon { + targetNode.iconNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 5.0, y: 5.0), size: image.size) + } else { + textFrame.origin.x += floor(image.size.width / 2.0) + targetNode.iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - image.size.height) * 0.5)), size: image.size) + } + if targetNode.iconNode.supernode == nil { + targetNode.addSubnode(targetNode.iconNode) + } + } else if targetNode.iconNode.supernode != nil { + targetNode.iconNode.removeFromSupernode() + } + + targetNode.textNode.frame = textFrame + targetNode.highlightedTextNode.frame = targetNode.textNode.frame + + return targetNode + }) + }) + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD new file mode 100644 index 0000000000..b1d57b8d4d --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD @@ -0,0 +1,46 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageAttachedContentNode", + module_name = "ChatMessageAttachedContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/UrlEscaping", + "//submodules/PhotoResources", + "//submodules/WebsiteType", + "//submodules/ChatMessageInteractiveMediaBadge", + "//submodules/GalleryData", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/ShimmerEffect", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode", + "//submodules/TelegramUI/Components/WallpaperPreviewMedia", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", + "//submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift similarity index 78% rename from submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 549cc9680a..c7a37b6233 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -23,256 +23,36 @@ import ChatMessageDateAndStatusNode import ChatHistoryEntry import ChatMessageItemCommon import ChatMessageBubbleContentNode +import ChatMessageInteractiveInstantVideoNode +import ChatMessageInteractiveFileNode +import ChatMessageInteractiveMediaNode +import WallpaperPreviewMedia +import ChatMessageAttachedContentButtonNode -private let buttonFont = Font.semibold(13.0) - -enum ChatMessageAttachedContentActionIcon { +public enum ChatMessageAttachedContentActionIcon { case instant case link } -struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { - var rawValue: Int32 +public struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { + public var rawValue: Int32 - init(rawValue: Int32) { + public init(rawValue: Int32) { self.rawValue = rawValue } - init() { + public init() { self.rawValue = 0 } - static let preferMediaInline = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 0) - static let preferMediaBeforeText = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 1) - static let preferMediaAspectFilled = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 2) - static let titleBeforeMedia = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 3) + public static let preferMediaInline = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 0) + public static let preferMediaBeforeText = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 1) + public static let preferMediaAspectFilled = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 2) + public static let titleBeforeMedia = ChatMessageAttachedContentNodeMediaFlags(rawValue: 1 << 3) } -final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode { - private let textNode: TextNode - private let iconNode: ASImageNode - private let highlightedTextNode: TextNode - private let backgroundNode: ASImageNode - private let shimmerEffectNode: ShimmerEffectForegroundNode - - private var regularImage: UIImage? - private var highlightedImage: UIImage? - private var regularIconImage: UIImage? - private var highlightedIconImage: UIImage? - - var pressed: (() -> Void)? - - private var titleColor: UIColor? - - init() { - self.textNode = TextNode() - self.textNode.isUserInteractionEnabled = false - self.highlightedTextNode = TextNode() - self.highlightedTextNode.isUserInteractionEnabled = false - - self.shimmerEffectNode = ShimmerEffectForegroundNode() - self.shimmerEffectNode.cornerRadius = 5.0 - self.shimmerEffectNode.isHidden = true - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.displaysAsynchronously = false - - self.iconNode = ASImageNode() - self.iconNode.isLayerBacked = true - self.iconNode.displayWithoutProcessing = true - self.iconNode.displaysAsynchronously = false - - super.init() - - self.addSubnode(self.shimmerEffectNode) - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textNode) - self.addSubnode(self.highlightedTextNode) - self.highlightedTextNode.isHidden = true - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.image = strongSelf.highlightedImage - strongSelf.iconNode.image = strongSelf.highlightedIconImage - strongSelf.textNode.isHidden = true - strongSelf.highlightedTextNode.isHidden = false - - let scale = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width - strongSelf.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false) - } else { - if let presentationLayer = strongSelf.layer.presentation() { - strongSelf.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false) - } - if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) { - strongSelf.view.addSubview(snapshot) - - snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in - snapshot.removeFromSuperview() - }) - } - - strongSelf.backgroundNode.image = strongSelf.regularImage - strongSelf.iconNode.image = strongSelf.regularIconImage - strongSelf.textNode.isHidden = false - strongSelf.highlightedTextNode.isHidden = true - } - } - } - - self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - } - - @objc func buttonPressed() { - self.pressed?() - } - - func startShimmering() { - guard let titleColor = self.titleColor else { - return - } - self.shimmerEffectNode.isHidden = false - self.shimmerEffectNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - let backgroundFrame = self.backgroundNode.frame - self.shimmerEffectNode.frame = backgroundFrame - self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: backgroundFrame.size), within: backgroundFrame.size) - self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: titleColor.withAlphaComponent(0.3), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) - } - - func stopShimmering() { - self.shimmerEffectNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in - self?.shimmerEffectNode.isHidden = true - }) - } - - static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> (_ width: CGFloat, _ regularImage: UIImage, _ highlightedImage: UIImage, _ iconImage: UIImage?, _ highlightedIconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ highlightedTitleColor: UIColor, _ inProgress: Bool) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode)) { - let previousRegularImage = current?.regularImage - let previousHighlightedImage = current?.highlightedImage - let previousRegularIconImage = current?.regularIconImage - let previousHighlightedIconImage = current?.highlightedIconImage - - let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout) - let maybeMakeHighlightedTextLayout = (current?.highlightedTextNode).flatMap(TextNode.asyncLayout) - - return { width, regularImage, highlightedImage, iconImage, highlightedIconImage, cornerIcon, title, titleColor, highlightedTitleColor, inProgress in - let targetNode: ChatMessageAttachedContentButtonNode - if let current = current { - targetNode = current - } else { - targetNode = ChatMessageAttachedContentButtonNode() - } - - let makeTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode) - if let maybeMakeTextLayout = maybeMakeTextLayout { - makeTextLayout = maybeMakeTextLayout - } else { - makeTextLayout = TextNode.asyncLayout(targetNode.textNode) - } - - let makeHighlightedTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode) - if let maybeMakeHighlightedTextLayout = maybeMakeHighlightedTextLayout { - makeHighlightedTextLayout = maybeMakeHighlightedTextLayout - } else { - makeHighlightedTextLayout = TextNode.asyncLayout(targetNode.highlightedTextNode) - } - - var updatedRegularImage: UIImage? - if regularImage !== previousRegularImage { - updatedRegularImage = regularImage - } - - var updatedHighlightedImage: UIImage? - if highlightedImage !== previousHighlightedImage { - updatedHighlightedImage = highlightedImage - } - - var updatedRegularIconImage: UIImage? - if iconImage !== previousRegularIconImage { - updatedRegularIconImage = iconImage - } - - var updatedHighlightedIconImage: UIImage? - if highlightedIconImage !== previousHighlightedIconImage { - updatedHighlightedIconImage = highlightedIconImage - } - - var iconWidth: CGFloat = 0.0 - if let iconImage = iconImage { - iconWidth = iconImage.size.width + 5.0 - } - - let labelInset: CGFloat = 8.0 - - let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) - - let (_, highlightedTextApply) = makeHighlightedTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: highlightedTitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) - - return (textSize.size.width + labelInset * 2.0, { refinedWidth in - return (CGSize(width: refinedWidth, height: 33.0), { - targetNode.accessibilityLabel = title - - targetNode.titleColor = titleColor - - if let updatedRegularImage = updatedRegularImage { - targetNode.regularImage = updatedRegularImage - if !targetNode.textNode.isHidden { - targetNode.backgroundNode.image = updatedRegularImage - } - } - if let updatedHighlightedImage = updatedHighlightedImage { - targetNode.highlightedImage = updatedHighlightedImage - if targetNode.textNode.isHidden { - targetNode.backgroundNode.image = updatedHighlightedImage - } - } - if let updatedRegularIconImage = updatedRegularIconImage { - targetNode.regularIconImage = updatedRegularIconImage - if !targetNode.textNode.isHidden { - targetNode.iconNode.image = updatedRegularIconImage - } - } - if let updatedHighlightedIconImage = updatedHighlightedIconImage { - targetNode.highlightedIconImage = updatedHighlightedIconImage - if targetNode.iconNode.isHidden { - targetNode.iconNode.image = updatedHighlightedIconImage - } - } - - let _ = textApply() - let _ = highlightedTextApply() - - let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: 33.0)) - var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((34.0 - textSize.size.height) / 2.0)), size: textSize.size) - targetNode.backgroundNode.frame = backgroundFrame - if let image = targetNode.iconNode.image { - if cornerIcon { - targetNode.iconNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 5.0, y: 5.0), size: image.size) - } else { - textFrame.origin.x += floor(image.size.width / 2.0) - targetNode.iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + 2.0), size: image.size) - } - if targetNode.iconNode.supernode == nil { - targetNode.addSubnode(targetNode.iconNode) - } - } else if targetNode.iconNode.supernode != nil { - targetNode.iconNode.removeFromSupernode() - } - - targetNode.textNode.frame = textFrame - targetNode.highlightedTextNode.frame = targetNode.textNode.frame - - return targetNode - }) - }) - } - } -} - -final class ChatMessageAttachedContentNode: ASDisplayNode { - private let lineNode: ASImageNode +public final class ChatMessageAttachedContentNode: HighlightTrackingButtonNode { + private var backgroundView: UIImageView? private let topTitleNode: TextNode private let textNode: TextNodeWithEntities private let inlineImageNode: TransformImageNode @@ -280,8 +60,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { private var contentInstantVideoNode: ChatMessageInteractiveInstantVideoNode? private var contentFileNode: ChatMessageInteractiveFileNode? private var buttonNode: ChatMessageAttachedContentButtonNode? + private var buttonSeparatorLayer: SimpleLayer? - let statusNode: ChatMessageDateAndStatusNode + public let statusNode: ChatMessageDateAndStatusNode private var additionalImageBadgeNode: ChatMessageInteractiveMediaBadge? private var linkHighlightingNode: LinkHighlightingNode? @@ -290,11 +71,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { private var media: Media? private var theme: ChatPresentationThemeData? - var openMedia: ((InteractiveMediaNodeActivateContent) -> Void)? - var activateAction: (() -> Void)? - var requestUpdateLayout: (() -> Void)? + public var openMedia: ((InteractiveMediaNodeActivateContent) -> Void)? + public var activateAction: (() -> Void)? + public var requestUpdateLayout: (() -> Void)? - var visibility: ListViewItemNodeVisibility = .none { + public var visibility: ListViewItemNodeVisibility = .none { didSet { if oldValue != self.visibility { self.contentImageNode?.visibility = self.visibility != .none @@ -313,12 +94,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - override init() { - self.lineNode = ASImageNode() - self.lineNode.isLayerBacked = true - self.lineNode.displaysAsynchronously = false - self.lineNode.displayWithoutProcessing = true - + public init() { self.topTitleNode = TextNode() self.topTitleNode.isUserInteractionEnabled = false self.topTitleNode.displaysAsynchronously = false @@ -333,24 +109,42 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { self.inlineImageNode = TransformImageNode() self.inlineImageNode.contentAnimations = [.subsequentUpdates] - self.inlineImageNode.isLayerBacked = !smartInvertColorsEnabled() + self.inlineImageNode.isLayerBacked = false self.inlineImageNode.displaysAsynchronously = false self.statusNode = ChatMessageDateAndStatusNode() - super.init() + super.init(pointerStyle: .default) - self.addSubnode(self.lineNode) self.addSubnode(self.topTitleNode) self.addSubnode(self.textNode.textNode) self.addSubnode(self.statusNode) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if self.bounds.width < 1.0 { + return + } + let transition: ContainedViewLayoutTransition = .animated(duration: highlighted ? 0.1 : 0.2, curve: .easeInOut) + let scale: CGFloat = highlighted ? ((self.bounds.width - 10.0) / self.bounds.width) : 1.0 + transition.updateSublayerTransformScale(node: self, scale: scale) + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) } - func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + @objc private func pressed() { + self.activateAction?() + } + + public func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let topTitleAsyncLayout = TextNode.asyncLayout(self.topTitleNode) let textAsyncLayout = TextNodeWithEntities.asyncLayout(self.textNode) let currentImage = self.media as? TelegramMediaImage + let currentMediaIsInline = self.inlineImageNode.supernode != nil let imageLayout = self.inlineImageNode.asyncLayout() let statusLayout = self.statusNode.asyncLayout() let contentImageLayout = ChatMessageInteractiveMediaNode.asyncLayout(self.contentImageNode) @@ -367,7 +161,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if message.adAttribute != nil { fontSize = floor(presentationData.fontSize.baseDisplaySize) } else { - fontSize = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0) + fontSize = floor(presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) } let titleFont = Font.semibold(fontSize) @@ -379,13 +173,14 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let textBlockQuoteFont = Font.regular(fontSize) var incoming = message.effectivelyIncoming(context.account.peerId) - if let subject = associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } var horizontalInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) if displayLine { - horizontalInsets.left += 12.0 + horizontalInsets.left += 10.0 + horizontalInsets.right += 9.0 } var titleBeforeMedia = false @@ -699,7 +494,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } else if let dimensions = largestImageRepresentation(image.representations)?.dimensions { inlineImageDimensions = dimensions.cgSize - if image != currentImage { + if image != currentImage || !currentMediaIsInline { updateInlineImageSignal = chatWebpageSnippetPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: .message(message: MessageReference(message), media: image)) } } @@ -733,7 +528,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } if let _ = inlineImageDimensions { - inlineImageSize = CGSize(width: 54.0, height: 54.0) + inlineImageSize = CGSize(width: 53.0, height: 53.0) if let inlineImageSize = inlineImageSize { textCutout.topRight = CGSize(width: inlineImageSize.width + 10.0, height: inlineImageSize.height + 10.0) @@ -741,14 +536,24 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } return (initialWidth, { constrainedSize, position in - var insets = UIEdgeInsets(top: 0.0, left: horizontalInsets.left, bottom: 5.0, right: horizontalInsets.right) - var lineInsets = insets + var insets = UIEdgeInsets(top: 0.0, left: horizontalInsets.left, bottom: 0.0, right: horizontalInsets.right) + switch position { - case .linear(.None, _): - insets.top += 8.0 - lineInsets.top += 8.0 + 8.0 + case let .linear(topNeighbor, bottomNeighbor): + switch topNeighbor { + case .None: + insets.top += 10.0 default: break + } + switch bottomNeighbor { + case .None: + insets.bottom += 12.0 + default: + insets.bottom += 0.0 + } + default: + break } let textConstrainedSize = CGSize(width: constrainedSize.width - insets.left - insets.right, height: constrainedSize.height - insets.top - insets.bottom) @@ -799,12 +604,41 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { textFrame = textFrame.offsetBy(dx: insets.left, dy: insets.top) - let lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(presentationData.theme.theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(presentationData.theme.theme) + let mainColor: UIColor + if !incoming { + mainColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor + } else { + var authorNameColor: UIColor? + let author = message.author + if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser { + authorNameColor = author.flatMap { chatMessagePeerIdColors[Int(clamping: $0.id.id._internalGetInt64Value() % 7)] } + if let rawAuthorNameColor = authorNameColor { + var dimColors = false + switch presentationData.theme.theme.name { + case .builtin(.nightAccent), .builtin(.night): + dimColors = true + default: + break + } + if dimColors { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + rawAuthorNameColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil) + authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0) + } + } + } + + if let authorNameColor { + mainColor = authorNameColor + } else { + mainColor = presentationData.theme.theme.chat.message.incoming.accentTextColor + } + } var boundingSize = textFrame.size - var lineHeight = textLayout.rawTextSize.height if titleBeforeMedia { - lineHeight += topTitleLayout.size.height + 4.0 boundingSize.height += topTitleLayout.size.height + 4.0 boundingSize.width = max(boundingSize.width, topTitleLayout.size.width) } @@ -812,9 +646,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if boundingSize.height < inlineImageSize.height { boundingSize.height = inlineImageSize.height } - if lineHeight < inlineImageSize.height { - lineHeight = inlineImageSize.height - } } if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { @@ -840,8 +671,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { boundingSize.width = max(boundingSize.width, videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight) } - lineHeight += lineInsets.top + lineInsets.bottom - var imageApply: (() -> Void)? if let inlineImageSize = inlineImageSize, let inlineImageDimensions = inlineImageDimensions { let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0)) @@ -849,18 +678,14 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { imageApply = imageLayout(arguments) } - var continueActionButtonLayout: ((CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode))? + var continueActionButtonLayout: ((CGFloat, CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode))? if let actionTitle = actionTitle, !isPreview { - let buttonImage: UIImage - let buttonHighlightedImage: UIImage var buttonIconImage: UIImage? var buttonHighlightedIconImage: UIImage? var cornerIcon = false let titleColor: UIColor let titleHighlightedColor: UIColor if incoming { - buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(presentationData.theme.theme)! - buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(presentationData.theme.theme)! if let actionIcon { switch actionIcon { case .instant: @@ -876,8 +701,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty) titleHighlightedColor = bubbleColor.fill[0] } else { - buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(presentationData.theme.theme)! - buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(presentationData.theme.theme)! if let actionIcon { switch actionIcon { case .instant: @@ -893,7 +716,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty) titleHighlightedColor = bubbleColor.fill[0] } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, buttonIconImage, buttonHighlightedIconImage, cornerIcon, actionTitle, titleColor, titleHighlightedColor, false) + let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, nil, buttonIconImage, buttonHighlightedIconImage, cornerIcon, actionTitle, titleColor, titleHighlightedColor, false) boundingSize.width = max(buttonWidth, boundingSize.width) continueActionButtonLayout = continueLayout } @@ -903,11 +726,10 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { return (boundingSize.width, { boundingWidth in var adjustedBoundingSize = boundingSize - var adjustedLineHeight = lineHeight var imageFrame: CGRect? if let inlineImageSize = inlineImageSize { - imageFrame = CGRect(origin: CGPoint(x: boundingWidth - inlineImageSize.width - insets.right, y: 0.0), size: inlineImageSize) + imageFrame = CGRect(origin: CGPoint(x: boundingWidth - inlineImageSize.width - insets.right + 4.0, y: 0.0), size: inlineImageSize) } var contentImageSizeAndApply: (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)? @@ -920,8 +742,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { imageHeightAddition += 2.0 } - adjustedBoundingSize.height += imageHeightAddition + 5.0 - adjustedLineHeight += imageHeightAddition + 4.0 + adjustedBoundingSize.height += imageHeightAddition + 7.0 } var contentFileSizeAndApply: (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode)? @@ -937,25 +758,26 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } adjustedBoundingSize.height += imageHeightAddition + 5.0 - adjustedLineHeight += imageHeightAddition + 4.0 } if let (videoLayout, _) = contentInstantVideoSizeAndApply { let imageHeightAddition = videoLayout.contentSize.height + 6.0 - if textFrame.size.height > CGFloat.ulpOfOne { - //imageHeightAddition += 2.0 - } adjustedBoundingSize.height += imageHeightAddition// + 5.0 - adjustedLineHeight += imageHeightAddition// + 4.0 } var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))? if let continueActionButtonLayout = continueActionButtonLayout { - let (size, apply) = continueActionButtonLayout(boundingWidth - 12.0 - insets.right) + let (size, apply) = continueActionButtonLayout(boundingWidth - 5.0 - insets.right, 38.0) actionButtonSizeAndApply = (size, apply) - adjustedBoundingSize.width = max(adjustedBoundingSize.width, insets.left + size.width + insets.right) - adjustedBoundingSize.height += 7.0 + size.height + adjustedBoundingSize.height += 4.0 + size.height + if let text, !text.isEmpty { + if contentImageSizeAndApply == nil { + adjustedBoundingSize.height += 5.0 + } else if let (_, flags) = mediaAndFlags, flags.contains(.preferMediaBeforeText) { + adjustedBoundingSize.height += 5.0 + } + } } var statusSizeAndApply: ((CGSize), (ListViewItemUpdateAnimation) -> Void)? @@ -964,7 +786,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } if let statusSizeAndApply = statusSizeAndApply { adjustedBoundingSize.height += statusSizeAndApply.0.height - adjustedLineHeight += statusSizeAndApply.0.height if let imageFrame = imageFrame, statusSizeAndApply.0.height == 0.0 { if statusInText { @@ -1007,7 +828,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { finalStatusFrame.origin.y += 14.0 adjustedBoundingSize.height += 14.0 - adjustedLineHeight += 14.0 } } } @@ -1021,9 +841,25 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { strongSelf.media = mediaAndFlags?.0 strongSelf.theme = presentationData.theme - strongSelf.lineNode.image = lineImage - animation.animator.updateFrame(layer: strongSelf.lineNode.layer, frame: CGRect(origin: CGPoint(x: 13.0, y: insets.top), size: CGSize(width: 2.0, height: adjustedLineHeight - insets.top - insets.bottom - 2.0)), completion: nil) - strongSelf.lineNode.isHidden = !displayLine + let backgroundView: UIImageView + if let current = strongSelf.backgroundView { + backgroundView = current + } else { + backgroundView = UIImageView() + strongSelf.backgroundView = backgroundView + strongSelf.view.insertSubview(backgroundView, at: 0) + } + + if backgroundView.image == nil { + backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(presentationData.theme.theme) + } + backgroundView.tintColor = mainColor + + animation.animator.updateFrame(layer: backgroundView.layer, frame: CGRect(origin: CGPoint(x: 11.0, y: insets.top - 3.0), size: CGSize(width: adjustedBoundingSize.width - 4.0 - insets.right, height: adjustedBoundingSize.height - insets.top - insets.bottom + 4.0)), completion: nil) + backgroundView.isHidden = !displayLine + + //strongSelf.borderColor = UIColor.red.cgColor + //strongSelf.borderWidth = 2.0 strongSelf.textNode.textNode.displaysAsynchronously = !isPreview @@ -1178,20 +1014,47 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let (size, apply) = actionButtonSizeAndApply { let buttonNode = apply() + + let buttonFrame = CGRect(origin: CGPoint(x: 12.0, y: adjustedBoundingSize.height - insets.bottom - size.height), size: size) if buttonNode !== strongSelf.buttonNode { strongSelf.buttonNode?.removeFromSupernode() strongSelf.buttonNode = buttonNode + buttonNode.isUserInteractionEnabled = false strongSelf.addSubnode(buttonNode) buttonNode.pressed = { if let strongSelf = self { strongSelf.activateAction?() } } + buttonNode.frame = buttonFrame + } else { + animation.animator.updateFrame(layer: buttonNode.layer, frame: buttonFrame, completion: nil) + } + + let buttonSeparatorFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + 8.0, y: buttonFrame.minY - 2.0), size: CGSize(width: buttonFrame.width - 8.0 - 8.0, height: UIScreenPixel)) + + let buttonSeparatorLayer: SimpleLayer + if let current = strongSelf.buttonSeparatorLayer { + buttonSeparatorLayer = current + animation.animator.updateFrame(layer: buttonSeparatorLayer, frame: buttonSeparatorFrame, completion: nil) + } else { + buttonSeparatorLayer = SimpleLayer() + strongSelf.buttonSeparatorLayer = buttonSeparatorLayer + strongSelf.layer.addSublayer(buttonSeparatorLayer) + buttonSeparatorLayer.frame = buttonSeparatorFrame + } + + buttonSeparatorLayer.backgroundColor = mainColor.withMultipliedAlpha(0.5).cgColor + } else { + if let buttonNode = strongSelf.buttonNode { + strongSelf.buttonNode = nil + buttonNode.removeFromSupernode() + } + + if let buttonSeparatorLayer = strongSelf.buttonSeparatorLayer { + strongSelf.buttonSeparatorLayer = nil + buttonSeparatorLayer.removeFromSuperlayer() } - buttonNode.frame = CGRect(origin: CGPoint(x: 12.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size) - } else if let buttonNode = strongSelf.buttonNode { - buttonNode.removeFromSupernode() - strongSelf.buttonNode = nil } } }) @@ -1200,7 +1063,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - func updateHiddenMedia(_ media: [Media]?) -> Bool { + public func updateHiddenMedia(_ media: [Media]?) -> Bool { if let currentMedia = self.media { if let media = media { var found = false @@ -1223,7 +1086,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { return false } - func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let contentImageNode = self.contentImageNode, let image = self.media as? TelegramMediaImage, image.isEqual(to: media) { return (contentImageNode, contentImageNode.bounds, { [weak contentImageNode] in return (contentImageNode?.view.snapshotContentTree(unhide: true), nil) @@ -1240,14 +1103,14 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { return nil } - func hasActionAtPoint(_ point: CGPoint) -> Bool { + public func hasActionAtPoint(_ point: CGPoint) -> Bool { if let buttonNode = self.buttonNode, buttonNode.frame.contains(point) { return true } return false } - func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.textNode.textNode.frame if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { @@ -1265,14 +1128,14 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { return .hashtag(hashtag.peerName, hashtag.hashtag) } else { - return .none + return .ignore } } else { - return .none + return .ignore } } - func updateTouchesAtPoint(_ point: CGPoint?) { + public func updateTouchesAtPoint(_ point: CGPoint?) { if let context = self.context, let message = self.message, let theme = self.theme { var rects: [CGRect]? if let point = point { @@ -1315,7 +1178,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.statusNode.isHidden { if let result = self.statusNode.reactionView(value: value) { return result @@ -1333,7 +1196,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { return nil } - func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { + public func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.contentImageNode?.playMediaWithSound() } } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift index 5e72fd305d..77bf4fe086 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentCalclulateImageCorners.swift @@ -2,10 +2,9 @@ import Foundation import UIKit import Display import TelegramPresentationData -import ChatMessageBubbleContentNode import ChatMessageItemCommon -func chatMessageBubbleImageContentCorners(relativeContentPosition position: ChatMessageBubbleContentPosition, normalRadius: CGFloat, mergedRadius: CGFloat, mergedWithAnotherContentRadius: CGFloat, layoutConstants: ChatMessageItemLayoutConstants, chatPresentationData: ChatPresentationData) -> ImageCorners { +public func chatMessageBubbleImageContentCorners(relativeContentPosition position: ChatMessageBubbleContentPosition, normalRadius: CGFloat, mergedRadius: CGFloat, mergedWithAnotherContentRadius: CGFloat, layoutConstants: ChatMessageItemLayoutConstants, chatPresentationData: ChatPresentationData) -> ImageCorners { let topLeftCorner: ImageCorner let topRightCorner: ImageCorner diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD new file mode 100644 index 0000000000..5b8b444a8d --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -0,0 +1,86 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageBubbleItemNode", + module_name = "ChatMessageBubbleItemNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/TemporaryCachedPeerDataManager", + "//submodules/LocalizedPeerData", + "//submodules/ContextUI", + "//submodules/TelegramUniversalVideoContent", + "//submodules/MosaicLayout", + "//submodules/TextSelectionNode", + "//submodules/PlatformRestrictionMatching", + "//submodules/Emoji", + "//submodules/PersistentStringHash", + "//submodules/GridMessageSelectionNode", + "//submodules/AppBundle", + "//submodules/Markdown", + "//submodules/WallpaperBackgroundNode", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ChatMessageBackground", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 574dc8cf00..072df7363b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -37,12 +37,38 @@ import ChatMessageTextBubbleContentNode import ChatMessageItemCommon import ChatMessageReplyInfoNode import ChatMessageCallBubbleContentNode - -enum InternalBubbleTapAction { - case action(() -> Void) - case optionalAction(() -> Void) - case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) -} +import ChatMessageInteractiveFileNode +import ChatMessageFileBubbleContentNode +import ChatMessageWebpageBubbleContentNode +import ChatMessagePollBubbleContentNode +import ChatMessageItem +import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageThreadInfoNode +import ChatMessageActionButtonsNode +import ChatSwipeToReplyRecognizer +import ChatMessageReactionsFooterContentNode +import ChatMessageInstantVideoBubbleContentNode +import ChatMessageCommentFooterContentNode +import ChatMessageActionBubbleContentNode +import ChatMessageContactBubbleContentNode +import ChatMessageEventLogPreviousDescriptionContentNode +import ChatMessageEventLogPreviousLinkContentNode +import ChatMessageEventLogPreviousMessageContentNode +import ChatMessageGameBubbleContentNode +import ChatMessageInvoiceBubbleContentNode +import ChatMessageMapBubbleContentNode +import ChatMessageMediaBubbleContentNode +import ChatMessageProfilePhotoSuggestionContentNode +import ChatMessageRestrictedBubbleContentNode +import ChatMessageStoryMentionContentNode +import ChatMessageUnsupportedBubbleContentNode +import ChatMessageWallpaperBubbleContentNode +import ChatMessageGiftBubbleContentNode +import ChatMessageGiveawayBubbleContentNode private struct BubbleItemAttributes { var isAttachment: Bool @@ -61,50 +87,6 @@ private final class ChatMessageBubbleClippingNode: ASDisplayNode { } } -func hasCommentButton(item: ChatMessageItem) -> Bool { - let firstMessage = item.content.firstMessage - - var hasDiscussion = false - if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { - hasDiscussion = true - } - if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id { - hasDiscussion = false - } - - if firstMessage.adAttribute != nil { - hasDiscussion = false - } - - if hasDiscussion { - var canComment = false - if case .pinnedMessages = item.associatedData.subject { - canComment = false - } else if firstMessage.id.namespace == Namespaces.Message.Local { - canComment = true - } else { - for attribute in firstMessage.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId { - switch item.associatedData.channelDiscussionGroup { - case .unknown: - canComment = true - case let .known(groupId): - canComment = groupId == commentsPeerId - } - break - } - } - } - - if canComment { - return true - } - } else if firstMessage.id.peerId.isReplies { - return true - } - return false -} - private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], Bool, Bool) { var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = [] var skipText = false @@ -273,7 +255,11 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ } } - result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) + if content.displayOptions.position == .aboveText { + result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)), at: 0) + } else { + result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) + } needReactions = false } break inner @@ -385,18 +371,18 @@ private func mapVisibility(_ visibility: ListViewItemNodeVisibility, boundsSize: } } -class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode { - class ContentContainer { - let contentMessageStableId: UInt32 - let sourceNode: ContextExtractedContentContainingNode - let containerNode: ContextControllerSourceNode - var backgroundWallpaperNode: ChatMessageBubbleBackdrop? - var backgroundNode: ChatMessageBackground? - var selectionBackgroundNode: ASDisplayNode? +public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode { + public class ContentContainer { + public let contentMessageStableId: UInt32 + public let sourceNode: ContextExtractedContentContainingNode + public let containerNode: ContextControllerSourceNode + public var backgroundWallpaperNode: ChatMessageBubbleBackdrop? + public var backgroundNode: ChatMessageBackground? + public var selectionBackgroundNode: ASDisplayNode? private var currentParams: (size: CGSize, contentOrigin: CGPoint, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, presentationContext: ChatPresentationContext, mediaBox: MediaBox, messageSelection: Bool?, selectionInsets: UIEdgeInsets)? - init(contentMessageStableId: UInt32) { + public init(contentMessageStableId: UInt32) { self.contentMessageStableId = contentMessageStableId self.sourceNode = ContextExtractedContentContainingNode() @@ -533,14 +519,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - let mainContextSourceNode: ContextExtractedContentContainingNode + public let mainContextSourceNode: ContextExtractedContentContainingNode private let mainContainerNode: ContextControllerSourceNode private let backgroundWallpaperNode: ChatMessageBubbleBackdrop private let backgroundNode: ChatMessageBackground private let shadowNode: ChatMessageShadowNode private var clippingNode: ChatMessageBubbleClippingNode - override var extractedBackgroundNode: ASDisplayNode? { + override public var extractedBackgroundNode: ASDisplayNode? { return self.shadowNode } @@ -554,7 +540,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode private var credibilityIconView: ComponentHostView? private var credibilityIconComponent: EmojiStatusComponent? private var forwardInfoNode: ChatMessageForwardInfoNode? - var forwardInfoReferenceNode: ASDisplayNode? { + public var forwardInfoReferenceNode: ASDisplayNode? { return self.forwardInfoNode } @@ -563,7 +549,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode private var contentContainersWrapperNode: ASDisplayNode private var contentContainers: [ContentContainer] = [] - private(set) var contentNodes: [ChatMessageBubbleContentNode] = [] + public private(set) var contentNodes: [ChatMessageBubbleContentNode] = [] private var mosaicStatusNode: ChatMessageDateAndStatusNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -590,7 +576,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode //private let debugNode: ASDisplayNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { if self.visibility != oldValue { for contentNode in self.contentNodes { @@ -625,7 +611,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - required init() { + required public init() { self.mainContextSourceNode = ContextExtractedContentContainingNode() self.mainContainerNode = ContextControllerSourceNode() self.backgroundWallpaperNode = ChatMessageBubbleBackdrop() @@ -773,11 +759,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func cancelInsertionAnimations() { + override public func cancelInsertionAnimations() { self.shadowNode.layer.removeAllAnimations() func process(node: ASDisplayNode) { @@ -811,7 +797,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode process(node: self) } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.shadowNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -846,7 +832,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode process(node: self) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { super.animateRemoved(currentTimestamp, duration: duration) self.allowsGroupOpacity = true @@ -858,7 +844,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: self.bounds.width / 2.0 - self.backgroundNode.frame.midX, y: self.backgroundNode.frame.midY), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { super.animateAdded(currentTimestamp, duration: duration) if let subnodes = self.subnodes { @@ -872,7 +858,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) { + public func animateFromLoadingPlaceholder(delay: Double, transition: ContainedViewLayoutTransition) { guard let item = self.item else { return } @@ -881,8 +867,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode transition.animatePositionAdditive(node: self, offset: CGPoint(x: incoming ? 30.0 : -30.0, y: -30.0), delay: delay) transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay) } + + public final class AnimationTransitionTextInput { + let backgroundView: UIView + let contentView: UIView + let sourceRect: CGRect + let scrollOffset: CGFloat - func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) { + public init(backgroundView: UIView, contentView: UIView, sourceRect: CGRect, scrollOffset: CGFloat) { + self.backgroundView = backgroundView + self.contentView = contentView + self.sourceRect = sourceRect + self.scrollOffset = scrollOffset + } + } + + public func animateContentFromTextInputField(textInput: AnimationTransitionTextInput, transition: CombinedTransition) { let widthDifference = self.backgroundNode.frame.width - textInput.backgroundView.frame.width let heightDifference = self.backgroundNode.frame.height - textInput.backgroundView.frame.height @@ -914,8 +914,26 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } } + + public final class AnimationTransitionReplyPanel { + public let titleNode: ASDisplayNode + public let textNode: ASDisplayNode + public let lineNode: ASDisplayNode + public let imageNode: ASDisplayNode + public let relativeSourceRect: CGRect + public let relativeTargetRect: CGRect - func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) { + public init(titleNode: ASDisplayNode, textNode: ASDisplayNode, lineNode: ASDisplayNode, imageNode: ASDisplayNode, relativeSourceRect: CGRect, relativeTargetRect: CGRect) { + self.titleNode = titleNode + self.textNode = textNode + self.lineNode = lineNode + self.imageNode = imageNode + self.relativeSourceRect = relativeSourceRect + self.relativeTargetRect = relativeTargetRect + } + } + + public func animateReplyPanel(sourceReplyPanel: AnimationTransitionReplyPanel, transition: CombinedTransition) { if let replyInfoNode = self.replyInfoNode { let localRect = self.mainContextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) let mappedPanel = ChatMessageReplyInfoNode.TransitionReplyPanel( @@ -930,7 +948,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - func animateFromMicInput(micInputNode: UIView, transition: CombinedTransition) -> ContextExtractedContentContainingNode? { + public func animateFromMicInput(micInputNode: UIView, transition: CombinedTransition) -> ContextExtractedContentContainingNode? { for contentNode in self.contentNodes { if let contentNode = contentNode as? ChatMessageFileBubbleContentNode { let statusContainerNode = contentNode.interactiveFileNode.statusContainerNode @@ -953,11 +971,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - func animateContentFromMediaInput(snapshotView: UIView, transition: CombinedTransition) { + public func animateContentFromMediaInput(snapshotView: UIView, transition: CombinedTransition) { self.mainContextSourceNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } - func animateContentFromGroupedMediaInput(transition: CombinedTransition) -> [CGRect] { + public func animateContentFromGroupedMediaInput(transition: CombinedTransition) -> [CGRect] { self.mainContextSourceNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) var rects: [CGRect] = [] @@ -969,12 +987,33 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return rects } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) recognizer.tapActionAtPoint = { [weak self] point in if let strongSelf = self { + if let item = strongSelf.item, let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject { + if case .link = info { + for contentNode in strongSelf.contentNodes { + let contentNodePoint = strongSelf.view.convert(point, to: contentNode.view) + let tapAction = contentNode.tapActionAtPoint(contentNodePoint, gesture: .tap, isEstimating: true) + switch tapAction { + case .none: + break + case .ignore: + return .fail + case .url: + return .waitForSingleTap + default: + break + } + } + } + + return .fail + } + if let shareButtonNode = strongSelf.shareButtonNode, shareButtonNode.frame.contains(point) { return .fail } @@ -1124,13 +1163,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.replyRecognizer = replyRecognizer self.view.addGestureRecognizer(replyRecognizer) - if let item = self.item, let subject = item.associatedData.subject, case .messageOptions = subject { - self.tapRecognizer?.isEnabled = false + if let item = self.item, let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject { + if case .link = info { + } else { + self.tapRecognizer?.isEnabled = false + } self.replyRecognizer?.isEnabled = false } } - override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { + override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = [] for contentNode in self.contentNodes { if let message = contentNode.item?.message { @@ -1239,7 +1281,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode do { let peerId = chatLocationPeerId - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { displayAuthorInfo = false } else if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { if let forwardInfo = item.content.firstMessage.forwardInfo { @@ -1913,7 +1955,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } let dateFormat: MessageTimestampStatusFormat - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { dateFormat = .minimal } else { dateFormat = .regular @@ -2215,6 +2257,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } + var updatedContentNodeOrder = false + if currentContentClassesPropertiesAndLayouts.count == contentNodeMessagesAndClasses.count { + for i in 0 ..< currentContentClassesPropertiesAndLayouts.count { + let currentClass: AnyClass = currentContentClassesPropertiesAndLayouts[i].1 + let contentItem = contentNodeMessagesAndClasses[i] as (message: Message, type: AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes) + if currentClass != contentItem.type { + updatedContentNodeOrder = true + break + } + } + } + var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, ChatMessageBubbleContentPosition?, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void), UInt32?, Bool?)] = [] var maxContentWidth: CGFloat = headerSize.width @@ -2627,6 +2681,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode replyInfoSizeApply: replyInfoSizeApply, replyInfoOriginY: replyInfoOriginY, removedContentNodeIndices: removedContentNodeIndices, + updatedContentNodeOrder: updatedContentNodeOrder, addedContentNodes: addedContentNodes, contentNodeMessagesAndClasses: contentNodeMessagesAndClasses, contentNodeFramesPropertiesAndApply: contentNodeFramesPropertiesAndApply, @@ -2677,6 +2732,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode replyInfoSizeApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode?), replyInfoOriginY: CGFloat, removedContentNodeIndices: [Int]?, + updatedContentNodeOrder: Bool, addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?, contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)], @@ -3179,7 +3235,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode strongSelf.contentContainersWrapperNode.view.mask = nil } - if removedContentNodeIndices?.count ?? 0 != 0 || addedContentNodes?.count ?? 0 != 0 { + if removedContentNodeIndices?.count ?? 0 != 0 || addedContentNodes?.count ?? 0 != 0 || updatedContentNodeOrder { var updatedContentNodes = strongSelf.contentNodes if let removedContentNodeIndices = removedContentNodeIndices { @@ -3553,8 +3609,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode strongSelf.mainContextSourceNode.layoutUpdated?(strongSelf.mainContextSourceNode.bounds.size, animation) } - if let subject = item.associatedData.subject, case .messageOptions = subject { - strongSelf.tapRecognizer?.isEnabled = false + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject { + if case .link = info { + } else { + strongSelf.tapRecognizer?.isEnabled = false + } strongSelf.replyRecognizer?.isEnabled = false strongSelf.mainContainerNode.isGestureEnabled = false for contentContainer in strongSelf.contentContainers { @@ -3571,7 +3630,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { + override public func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { super.updateAccessibilityData(accessibilityData) self.messageAccessibilityArea.accessibilityLabel = accessibilityData.label @@ -3611,7 +3670,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func shouldAnimateHorizontalFrameTransition() -> Bool { + override public func shouldAnimateHorizontalFrameTransition() -> Bool { return false /*if let _ = self.backgroundFrameTransition { return true @@ -3620,7 +3679,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode }*/ } - override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { + override public func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { super.animateFrameTransition(progress, currentValue) /*if let backgroundFrameTransition = self.backgroundFrameTransition { @@ -3665,7 +3724,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode }*/ } - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let item = self.item { @@ -4108,7 +4167,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil } @@ -4158,7 +4217,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return super.hitTest(point, with: event) } - override func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { for contentNode in self.contentNodes { if let result = contentNode.transitionNode(messageId: id, media: media, adjustRect: adjustRect) { if self.contentNodes.count == 1 && self.contentNodes.first is ChatMessageMediaBubbleContentNode && self.nameNode == nil && self.adminBadgeNode == nil && self.forwardInfoNode == nil && self.replyInfoNode == nil { @@ -4200,7 +4259,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - override func updateHiddenMedia() { + override public func updateHiddenMedia() { var hasHiddenMosaicStatus = false var hasHiddenBackground = false if let item = self.item { @@ -4233,7 +4292,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.backgroundWallpaperNode.isHidden = hasHiddenBackground } - override func updateAutomaticMediaDownloadSettings() { + override public func updateAutomaticMediaDownloadSettings() { if let item = self.item { for contentNode in self.contentNodes { contentNode.updateAutomaticMediaDownloadSettings(item.controllerInteraction.automaticMediaDownloadSettings) @@ -4241,7 +4300,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { + override public func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { for contentNode in self.contentNodes { if let playMediaWithSound = contentNode.playMediaWithSound() { return playMediaWithSound @@ -4250,7 +4309,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { guard let item = self.item else { return } @@ -4363,13 +4422,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func updateSearchTextHighlightState() { + override public func updateSearchTextHighlightState() { for contentNode in self.contentNodes { contentNode.updateSearchTextHighlightState(text: self.item?.controllerInteraction.searchTextHighightState?.0, messages: self.item?.controllerInteraction.searchTextHighightState?.1) } } - override func updateHighlightedState(animated: Bool) { + override public func updateHighlightedState(animated: Bool) { super.updateHighlightedState(animated: animated) guard let item = self.item, let _ = self.backgroundType else { @@ -4402,7 +4461,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - @objc func shareButtonPressed() { + @objc private func shareButtonPressed() { if let item = self.item { if case .pinnedMessages = item.associatedData.subject { item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id) @@ -4430,7 +4489,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } private var playedSwipeToReplyHaptic = false - @objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { + @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 var leftOffset: CGFloat = 0.0 var swipeOffset: CGFloat = 45.0 @@ -4561,7 +4620,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode private var absoluteRect: (CGRect, CGSize)? - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) guard !self.mainContextSourceNode.isExtractedToContextPreview else { return @@ -4612,7 +4671,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if !self.mainContextSourceNode.isExtractedToContextPreview { self.applyAbsoluteOffsetInternal(value: CGPoint(x: -value.x, y: -value.y), animationCurve: animationCurve, duration: duration) } @@ -4642,7 +4701,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { + override public func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { if self.contentContainers.count > 1 { return self.contentContainers.first(where: { $0.contentMessageStableId == stableId })?.sourceNode ?? self.mainContextSourceNode } else { @@ -4650,7 +4709,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + override public func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.mainContextSourceNode.contentNode.addSubnode(accessoryItemNode) } @@ -4660,7 +4719,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return self.mainContextSourceNode.isExtractedToContextPreview || hasWallpaper || isPreview || !self.disablesComments } - override func openMessageContextMenu() { + override public func openMessageContextMenu() { guard let item = self.item else { return } @@ -4668,7 +4727,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil, nil) } - override func targetReactionView(value: MessageReaction.Reaction) -> UIView? { + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result } @@ -4683,7 +4742,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - override func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: StoryId) -> UIView? { guard let item = self.item else { return nil } @@ -4704,13 +4763,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - override func unreadMessageRangeUpdated() { + override public func unreadMessageRangeUpdated() { for contentNode in self.contentNodes { contentNode.unreadMessageRangeUpdated() } } - func animateQuizInvalidOptionSelected() { + public func animateQuizInvalidOptionSelected() { if let supernode = self.supernode, let subnodes = supernode.subnodes { for i in 0 ..< subnodes.count { if subnodes[i] === self { @@ -4767,7 +4826,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.layer.add(animation, forKey: "quizInvalidRotation") } - func updatePsaTooltipMessageState(animated: Bool) { + public func updatePsaTooltipMessageState(animated: Bool) { guard let item = self.item else { return } @@ -4776,7 +4835,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - override func getStatusNode() -> ASDisplayNode? { + override public func getStatusNode() -> ASDisplayNode? { for contentNode in self.contentNodes { if let statusNode = contentNode.getStatusNode() { return statusNode @@ -4788,7 +4847,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return nil } - func hasExpandedAudioTranscription() -> Bool { + public func hasExpandedAudioTranscription() -> Bool { for contentNode in self.contentNodes { if let contentNode = contentNode as? ChatMessageFileBubbleContentNode { return contentNode.interactiveFileNode.hasExpandedAudioTranscription @@ -4799,7 +4858,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode return false } - override func contentFrame() -> CGRect { + override public func contentFrame() -> CGRect { return self.backgroundNode.frame } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD new file mode 100644 index 0000000000..488db161a5 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageCommentFooterContentNode", + module_name = "ChatMessageCommentFooterContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/RadialStatusNode", + "//submodules/AnimatedCountLabelNode", + "//submodules/AnimatedAvatarSetNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift index a4dc2044b1..a1622f69cf 100644 --- a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift @@ -12,7 +12,7 @@ import AnimatedAvatarSetNode import ChatMessageBubbleContentNode import ChatMessageItemCommon -final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { private let separatorNode: ASDisplayNode private let countNode: AnimatedCountLabelNode private let alternativeCountNode: AnimatedCountLabelNode @@ -24,7 +24,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { private let unreadIconNode: ASImageNode private var statusNode: RadialStatusNode? - required init() { + required public init() { self.separatorNode = ASDisplayNode() self.separatorNode.isUserInteractionEnabled = false @@ -83,7 +83,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -98,7 +98,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { } } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeCountLayout = self.countNode.asyncLayout() let makeAlternativeCountLayout = self.alternativeCountNode.asyncLayout() @@ -392,30 +392,30 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.buttonNode.frame.contains(point) { return .ignore } return .none } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.buttonNode.isUserInteractionEnabled && self.buttonNode.frame.contains(point) { return self.buttonNode.view } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD new file mode 100644 index 0000000000..612b19bfac --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageContactBubbleContentNode", + module_name = "ChatMessageContactBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AvatarNode", + "//submodules/AccountContext", + "//submodules/PhoneNumberFormat", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift similarity index 93% rename from submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index 75cac2f2e5..29d71ac435 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -12,13 +12,14 @@ import PhoneNumberFormat import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentButtonNode private let avatarFont = avatarPlaceholderFont(size: 16.0) private let titleFont = Font.medium(14.0) private let textFont = Font.regular(14.0) -class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { private let avatarNode: AvatarNode private let dateAndStatusNode: ChatMessageDateAndStatusNode private let titleNode: TextNode @@ -29,7 +30,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { private let buttonNode: ChatMessageAttachedContentButtonNode - required init() { + required public init() { self.avatarNode = AvatarNode(font: avatarFont) self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.titleNode = TextNode() @@ -62,23 +63,23 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { self.buttonPressed() return true } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func didLoad() { + override public func didLoad() { super.didLoad() let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.contactTap(_:))) self.view.addGestureRecognizer(tapRecognizer) } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let statusLayout = self.dateAndStatusNode.asyncLayout() let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) @@ -96,7 +97,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } var incoming = item.message.effectivelyIncoming(item.context.account.peerId) - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } @@ -283,7 +284,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { return (contentWidth, { boundingWidth in let baseAvatarFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right, y: layoutConstants.text.bubbleInsets.top), size: avatarSize) - let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0) + let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, 33.0) let buttonSpacing: CGFloat = 4.0 let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets) @@ -377,19 +378,19 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.buttonNode.frame.contains(point) { return .openMessage } @@ -399,7 +400,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { return .none } - @objc func contactTap(_ recognizer: UITapGestureRecognizer) { + @objc private func contactTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { if let item = self.item { let _ = item.controllerInteraction.openMessage(item.message, .default) @@ -413,14 +414,14 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.dateAndStatusNode.isHidden { return self.dateAndStatusNode.reactionView(value: value) } return nil } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.dateAndStatusNode.supernode != nil, let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) { return result } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index 23c64b32cc..b62393f58e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -87,7 +87,7 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess } } - if let subject = associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { authorTitle = nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD new file mode 100644 index 0000000000..526ff0cdbc --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageDeliveryFailedNode", + module_name = "ChatMessageDeliveryFailedNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageDeliveryFailedNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/Sources/ChatMessageDeliveryFailedNode.swift similarity index 81% rename from submodules/TelegramUI/Sources/ChatMessageDeliveryFailedNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/Sources/ChatMessageDeliveryFailedNode.swift index 9a44437b6a..caec43e2cd 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDeliveryFailedNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/Sources/ChatMessageDeliveryFailedNode.swift @@ -4,11 +4,11 @@ import AsyncDisplayKit import Display import TelegramPresentationData -final class ChatMessageDeliveryFailedNode: ASImageNode { +public final class ChatMessageDeliveryFailedNode: ASImageNode { private let tapped: () -> Void private var theme: PresentationTheme? - init(tapped: @escaping () -> Void) { + public init(tapped: @escaping () -> Void) { self.tapped = tapped super.init() @@ -18,7 +18,7 @@ final class ChatMessageDeliveryFailedNode: ASImageNode { self.isUserInteractionEnabled = true } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } @@ -29,7 +29,7 @@ final class ChatMessageDeliveryFailedNode: ASImageNode { } } - func updateLayout(theme: PresentationTheme) -> CGSize { + public func updateLayout(theme: PresentationTheme) -> CGSize { if self.theme !== theme { self.theme = theme self.image = PresentationResourcesChat.chatBubbleDeliveryFailedIcon(theme) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD new file mode 100644 index 0000000000..8ac301a11e --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageEventLogPreviousDescriptionContentNode", + module_name = "ChatMessageEventLogPreviousDescriptionContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift similarity index 73% rename from submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift index c6e6ad4df0..8421fef52c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -7,17 +7,18 @@ import SwiftSignalKit import TelegramCore import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentNode -final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubbleContentNode { private let contentNode: ChatMessageAttachedContentNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.contentNode.visibility = visibility } } - required init() { + required public init() { self.contentNode = ChatMessageAttachedContentNode() super.init() @@ -25,11 +26,11 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble self.addSubnode(self.contentNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let contentNodeLayout = self.contentNode.asyncLayout() return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -75,23 +76,23 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { /*if let webPage = self.webPage, case let .Loaded(content) = webPage.content { if content.instantPage != nil { @@ -102,11 +103,11 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble return .none } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD new file mode 100644 index 0000000000..db002d4d41 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageEventLogPreviousLinkContentNode", + module_name = "ChatMessageEventLogPreviousLinkContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift similarity index 73% rename from submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift index 44dbc44d16..a6d21075d6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift @@ -7,17 +7,18 @@ import SwiftSignalKit import TelegramCore import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentNode -final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContentNode { private let contentNode: ChatMessageAttachedContentNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.contentNode.visibility = visibility } } - required init() { + required public init() { self.contentNode = ChatMessageAttachedContentNode() super.init() @@ -25,11 +26,11 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent self.addSubnode(self.contentNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let contentNodeLayout = self.contentNode.asyncLayout() return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -70,23 +71,23 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { /*if let webPage = self.webPage, case let .Loaded(content) = webPage.content { if content.instantPage != nil { @@ -97,17 +98,14 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent return .none } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } return self.contentNode.transitionNode(media: media) } } - - - diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD new file mode 100644 index 0000000000..0a07f4940e --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageEventLogPreviousMessageContentNode", + module_name = "ChatMessageEventLogPreviousMessageContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift similarity index 74% rename from submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift index 230ed19c27..ef73309cae 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift @@ -7,17 +7,18 @@ import SwiftSignalKit import TelegramCore import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentNode -final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleContentNode { private let contentNode: ChatMessageAttachedContentNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.contentNode.visibility = visibility } } - required init() { + required public init() { self.contentNode = ChatMessageAttachedContentNode() super.init() @@ -25,11 +26,11 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont self.addSubnode(self.contentNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let contentNodeLayout = self.contentNode.asyncLayout() return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -75,23 +76,23 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { let contentNodeFrame = self.contentNode.frame return self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating) @@ -99,16 +100,16 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont return .none } - override func updateTouchesAtPoint(_ point: CGPoint?) { + override public func updateTouchesAtPoint(_ point: CGPoint?) { let contentNodeFrame = self.contentNode.frame self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) }) } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD new file mode 100644 index 0000000000..80e45ae39a --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageFileBubbleContentNode", + module_name = "ChatMessageFileBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramUIPreferences", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AudioTranscriptionButtonComponent", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift index dd588ca48e..029ddcf1ec 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift @@ -11,11 +11,12 @@ import AudioTranscriptionButtonComponent import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageInteractiveFileNode -class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { - let interactiveFileNode: ChatMessageInteractiveFileNode +public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { + public let interactiveFileNode: ChatMessageInteractiveFileNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { var wasVisible = false if case .visible = oldValue { @@ -31,7 +32,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - required init() { + required public init() { self.interactiveFileNode = ChatMessageInteractiveFileNode() super.init() @@ -83,18 +84,18 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { if let item = self.item { let _ = item.controllerInteraction.openMessage(item.message, .default) } return true } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let interactiveFileLayout = self.interactiveFileNode.asyncLayout() return { item, layoutConstants, preparePosition, selection, constrainedSize, _ in @@ -106,7 +107,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } var incoming = item.message.effectivelyIncoming(item.context.account.peerId) - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } let statusType: ChatMessageDateAndStatusType? @@ -186,7 +187,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return self.interactiveFileNode.transitionNode(media: media) } else { @@ -194,31 +195,31 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return self.interactiveFileNode.updateHiddenMedia(media) } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.interactiveFileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.interactiveFileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func willUpdateIsExtractedToContextPreview(_ value: Bool) { + override public func willUpdateIsExtractedToContextPreview(_ value: Bool) { self.interactiveFileNode.willUpdateIsExtractedToContextPreview(value) } - override func updateIsExtractedToContextPreview(_ value: Bool) { + override public func updateIsExtractedToContextPreview(_ value: Bool) { self.interactiveFileNode.updateIsExtractedToContextPreview(value) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) { return .ignore } @@ -228,14 +229,14 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating) } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.interactiveFileNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.view), with: event) { return result } return super.hitTest(point, with: event) } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.interactiveFileNode.dateAndStatusNode.isHidden { return self.interactiveFileNode.dateAndStatusNode.reactionView(value: value) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD new file mode 100644 index 0000000000..8f178acb21 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageGameBubbleContentNode", + module_name = "ChatMessageGameBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift similarity index 76% rename from submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index ece75f7847..fdd14b3f7a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -7,19 +7,20 @@ import SwiftSignalKit import TelegramCore import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentNode -final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { private var game: TelegramMediaGame? private let contentNode: ChatMessageAttachedContentNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.contentNode.visibility = visibility } } - required init() { + required public init() { self.contentNode = ChatMessageAttachedContentNode() super.init() @@ -32,18 +33,18 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { } } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { if let item = self.item { item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false) } return true } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let contentNodeLayout = self.contentNode.asyncLayout() return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -104,23 +105,23 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { /*if let webPage = self.webPage, case let .Loaded(content) = webPage.content { if content.instantPage != nil { @@ -131,18 +132,18 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { return .none } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } return self.contentNode.transitionNode(media: media) } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.contentNode.statusNode.isHidden { return self.contentNode.statusNode.reactionView(value: value) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD new file mode 100644 index 0000000000..ba1ade2779 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageGiftBubbleContentNode", + module_name = "ChatMessageGiftBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/LocalizedPeerData", + "//submodules/UrlEscaping", + "//submodules/TelegramStringFormatting", + "//submodules/WallpaperBackgroundNode", + "//submodules/ReactionSelectionNode", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/ShimmerEffect", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index bd7f643d36..6dacdacebc 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -25,7 +25,7 @@ private func attributedServiceMessageString(theme: ChatPresentationThemeData, st return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false) } -class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let labelNode: TextNode private var backgroundNode: WallpaperBubbleBackgroundNode? private var backgroundColorNode: ASDisplayNode @@ -49,7 +49,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private var isPlaying: Bool = false - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none let isVisible = self.visibility != .none @@ -71,7 +71,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private var animationDisposable: Disposable? private var setupTimestamp: Double? - required init() { + required public init() { self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false self.labelNode.displaysAsynchronously = false @@ -143,7 +143,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -189,7 +189,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) @@ -421,7 +421,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if let mediaBackgroundContent = self.mediaBackgroundContent { @@ -441,19 +441,19 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let backgroundNode = self.backgroundNode { backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } } - override func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + override public func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { if let backgroundNode = self.backgroundNode { backgroundNode.offsetSpring(value: value, duration: duration, damping: damping) } } - override func updateTouchesAtPoint(_ point: CGPoint?) { + override public func updateTouchesAtPoint(_ point: CGPoint?) { if let item = self.item { var rects: [(CGRect, CGRect)]? let textNodeFrame = self.labelNode.frame @@ -505,7 +505,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.labelNode.frame if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { @@ -536,7 +536,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - override func unreadMessageRangeUpdated() { + override public func unreadMessageRangeUpdated() { self.updateVisibility() } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD new file mode 100644 index 0000000000..12c33d4602 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD @@ -0,0 +1,35 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageGiveawayBubbleContentNode", + module_name = "ChatMessageGiveawayBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AvatarNode", + "//submodules/AccountContext", + "//submodules/PhoneNumberFormat", + "//submodules/TelegramStringFormatting", + "//submodules/Markdown", + "//submodules/ShimmerEffect", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageGiveawayBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 6bfa330263..a118d57ac6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -14,17 +14,17 @@ import Markdown import ShimmerEffect import AnimatedStickerNode import TelegramAnimatedStickerNode -import AvatarNode import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentButtonNode import UndoUI private let titleFont = Font.medium(15.0) private let textFont = Font.regular(13.0) private let boldTextFont = Font.semibold(13.0) -class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { private let dateAndStatusNode: ChatMessageDateAndStatusNode private let placeholderNode: StickerShimmerEffectNode @@ -49,7 +49,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { private let buttonNode: ChatMessageAttachedContentButtonNode private let channelButton: PeerButtonNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none let isVisible = self.visibility != .none @@ -70,7 +70,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { private var setupTimestamp: Double? - required init() { + required public init() { self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode.isUserInteractionEnabled = false self.placeholderNode.alpha = 0.75 @@ -131,16 +131,16 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { } } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { self.buttonPressed() return true } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func didLoad() { + override public func didLoad() { super.didLoad() let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.bubbleTap(_:))) @@ -168,7 +168,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { } } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let statusLayout = self.dateAndStatusNode.asyncLayout() let makePrizeTitleLayout = TextNode.asyncLayout(self.prizeTitleNode) let makePrizeTextLayout = TextNode.asyncLayout(self.prizeTextNode) @@ -203,7 +203,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { } var incoming = item.message.effectivelyIncoming(item.context.account.peerId) - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } @@ -437,7 +437,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right * 2.0 return (contentWidth, { boundingWidth in - let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0) + let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, 33.0) let buttonSpacing: CGFloat = 4.0 let (channelButtonSize, channelButtonApply) = continueChannelLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0) @@ -560,25 +560,25 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { } private var absoluteRect: (CGRect, CGSize)? - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize) } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.buttonNode.frame.contains(point) { return .ignore } @@ -598,14 +598,14 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode { } } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.dateAndStatusNode.isHidden { return self.dateAndStatusNode.reactionView(value: value) } return nil } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.dateAndStatusNode.supernode != nil, let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) { return result } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD new file mode 100644 index 0000000000..ce0497ba6e --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInstantVideoBubbleContentNode", + module_name = "ChatMessageInstantVideoBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramUIPreferences", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AudioTranscriptionButtonComponent", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift similarity index 89% rename from submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift index 0c0ef13528..6f6dd71eb5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -11,10 +11,26 @@ import AudioTranscriptionButtonComponent import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageInteractiveInstantVideoNode +import ChatMessageInteractiveFileNode -class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { - let interactiveFileNode: ChatMessageInteractiveFileNode - let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode +extension ChatMessageInteractiveInstantVideoNode.AnimateFileNodeDescription { + convenience init(_ node: ChatMessageInteractiveFileNode) { + self.init( + node: node, + textClippingNode: node.textClippingNode, + dateAndStatusNode: node.dateAndStatusNode, + fetchingTextNode: node.fetchingTextNode, + waveformView: node.waveformView, + statusNode: node.statusNode, + audioTranscriptionButton: node.audioTranscriptionButton + ) + } +} + +public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { + public let interactiveFileNode: ChatMessageInteractiveFileNode + public let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode private let maskLayer = SimpleLayer() private let maskForeground = SimpleLayer() @@ -26,7 +42,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { private var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed - var hasExpandedAudioTranscription: Bool { + public var hasExpandedAudioTranscription: Bool { if case .expanded = self.audioTranscriptionState { return true } else { @@ -34,7 +50,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { } } - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { var wasVisible = false if case .visible = oldValue { @@ -63,7 +79,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { return isVisible } - required init() { + required public init() { self.interactiveFileNode = ChatMessageInteractiveFileNode() self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode() @@ -148,18 +164,18 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { } } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { if let item = self.item { let _ = item.controllerInteraction.openMessage(item.message, .default) } return true } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let interactiveVideoLayout = self.interactiveVideoNode.asyncLayout() let interactiveFileLayout = self.interactiveFileNode.asyncLayout() @@ -176,7 +192,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { } var incoming = item.message.effectivelyIncoming(item.context.account.peerId) - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } @@ -355,9 +371,9 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { if currentExpanded != isExpanded { if isExpanded { - strongSelf.interactiveVideoNode.animateTo(strongSelf.interactiveFileNode, animator: animation.animator) + strongSelf.interactiveVideoNode.animateTo(ChatMessageInteractiveInstantVideoNode.AnimateFileNodeDescription(strongSelf.interactiveFileNode), animator: animation.animator) } else { - strongSelf.interactiveVideoNode.animateFrom(strongSelf.interactiveFileNode, animator: animation.animator) + strongSelf.interactiveVideoNode.animateFrom(ChatMessageInteractiveInstantVideoNode.AnimateFileNodeDescription(strongSelf.interactiveFileNode), animator: animation.animator) } } } @@ -367,35 +383,35 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return false } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.interactiveVideoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.interactiveVideoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.interactiveVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func willUpdateIsExtractedToContextPreview(_ value: Bool) { + override public func willUpdateIsExtractedToContextPreview(_ value: Bool) { self.interactiveFileNode.willUpdateIsExtractedToContextPreview(value) } - override func updateIsExtractedToContextPreview(_ value: Bool) { + override public func updateIsExtractedToContextPreview(_ value: Bool) { self.interactiveFileNode.updateIsExtractedToContextPreview(value) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if !self.interactiveFileNode.isHidden { if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) { return .ignore @@ -415,7 +431,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating) } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.isExpanded, let result = self.interactiveFileNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.view), with: event) { return result } @@ -425,18 +441,18 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { return super.hitTest(point, with: event) } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.interactiveVideoNode.dateAndStatusNode.isHidden { return self.interactiveVideoNode.dateAndStatusNode.reactionView(value: value) } return nil } - override func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: StoryId) -> UIView? { return self.interactiveVideoNode.targetForStoryTransition(id: id) } - override var disablesClipping: Bool { + override public var disablesClipping: Bool { return true } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD new file mode 100644 index 0000000000..5716eabe19 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInstantVideoItemNode", + module_name = "ChatMessageInstantVideoItemNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/ContextUI", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index 4ffc648752..5d173e2ee2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -18,54 +18,64 @@ import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageBubbleContentNode import ChatMessageReplyInfoNode +import ChatMessageInteractiveInstantVideoNode +import ChatMessageItem +import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageActionButtonsNode +import ChatSwipeToReplyRecognizer +import ChatMessageReactionsFooterContentNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont -class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerDelegate { - let contextSourceNode: ContextExtractedContentContainingNode - let containerNode: ContextControllerSourceNode - let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode +public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerDelegate { + public let contextSourceNode: ContextExtractedContentContainingNode + public let containerNode: ContextControllerSourceNode + public let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode - var selectionNode: ChatMessageSelectionNode? - var deliveryFailedNode: ChatMessageDeliveryFailedNode? - var shareButtonNode: ChatMessageShareButton? + public var selectionNode: ChatMessageSelectionNode? + public var deliveryFailedNode: ChatMessageDeliveryFailedNode? + public var shareButtonNode: ChatMessageShareButton? - var swipeToReplyNode: ChatMessageSwipeToReplyNode? - var swipeToReplyFeedback: HapticFeedback? + public var swipeToReplyNode: ChatMessageSwipeToReplyNode? + public var swipeToReplyFeedback: HapticFeedback? - var appliedParams: ListViewItemLayoutParams? - var appliedItem: ChatMessageItem? - var appliedForwardInfo: (Peer?, String?)? - var appliedHasAvatar = false - var appliedCurrentlyPlaying: Bool? - var appliedAutomaticDownload = false - var avatarOffset: CGFloat? + public var appliedParams: ListViewItemLayoutParams? + public var appliedItem: ChatMessageItem? + public var appliedForwardInfo: (Peer?, String?)? + public var appliedHasAvatar = false + public var appliedCurrentlyPlaying: Bool? + public var appliedAutomaticDownload = false + public var avatarOffset: CGFloat? - var animatingHeight: Bool { + public var animatingHeight: Bool { return self.apparentHeightTransition != nil } - var viaBotNode: TextNode? - var replyInfoNode: ChatMessageReplyInfoNode? - var replyBackgroundNode: NavigationBackgroundNode? - var forwardInfoNode: ChatMessageForwardInfoNode? + public var viaBotNode: TextNode? + public var replyInfoNode: ChatMessageReplyInfoNode? + public var replyBackgroundNode: NavigationBackgroundNode? + public var forwardInfoNode: ChatMessageForwardInfoNode? - var actionButtonsNode: ChatMessageActionButtonsNode? - var reactionButtonsNode: ChatMessageReactionButtonsNode? + public var actionButtonsNode: ChatMessageActionButtonsNode? + public var reactionButtonsNode: ChatMessageReactionButtonsNode? - let messageAccessibilityArea: AccessibilityAreaNode + public let messageAccessibilityArea: AccessibilityAreaNode - var currentSwipeToReplyTranslation: CGFloat = 0.0 + public var currentSwipeToReplyTranslation: CGFloat = 0.0 - var recognizer: TapLongTapOrDoubleTapGestureRecognizer? + public var recognizer: TapLongTapOrDoubleTapGestureRecognizer? private var replyRecognizer: ChatSwipeToReplyRecognizer? - var currentSwipeAction: ChatControllerInteractionSwipeAction? + public var currentSwipeAction: ChatControllerInteractionSwipeAction? - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none let isVisible = self.visibility != .none @@ -79,7 +89,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD fileprivate var wasPlaying = false - required init() { + required public init() { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode() @@ -172,11 +182,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -235,7 +245,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD self.view.disablesInteractiveTransitionGestureRecognizer = true } - override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { + override public func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { super.updateAccessibilityData(accessibilityData) self.messageAccessibilityArea.accessibilityLabel = accessibilityData.label @@ -266,7 +276,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { + override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { let layoutConstants = self.layoutConstants let makeVideoLayout = self.interactiveVideoNode.asyncLayout() @@ -917,7 +927,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { @@ -995,7 +1005,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD return nil } - @objc func shareButtonPressed() { + @objc private func shareButtonPressed() { if let item = self.item { if case .pinnedMessages = item.associatedData.subject { item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id) @@ -1027,7 +1037,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } private var playedSwipeToReplyHaptic = false - @objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { + @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 var leftOffset: CGFloat = 0.0 var swipeOffset: CGFloat = 45.0 @@ -1149,7 +1159,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view } @@ -1159,7 +1169,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD return super.hitTest(point, with: event) } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { guard let item = self.item else { return } @@ -1228,29 +1238,29 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - override func cancelInsertionAnimations() { + override public func cancelInsertionAnimations() { self.layer.removeAllAnimations() } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { super.animateRemoved(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { super.animateAdded(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) { + public func animateFromLoadingPlaceholder(delay: Double, transition: ContainedViewLayoutTransition) { guard let item = self.item else { return } @@ -1260,24 +1270,24 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay) } - func animateFromSnapshot(snapshotView: UIView, transition: CombinedTransition) { + public func animateFromSnapshot(snapshotView: UIView, transition: CombinedTransition) { snapshotView.frame = self.interactiveVideoNode.view.convert(snapshotView.frame, from: self.contextSourceNode.contentNode.view) self.interactiveVideoNode.animateFromSnapshot(snapshotView: snapshotView, transition: transition) } - override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { + override public func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.interactiveVideoNode.playMediaWithSound() } - override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { + override public func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { return self.contextSourceNode } - override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + override public func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } - override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { + override public func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { super.animateFrameTransition(progress, currentValue) guard let item = self.appliedItem, let params = self.appliedParams, progress > 0.0, let (initialHeight, targetHeight) = self.apparentHeightTransition, !targetHeight.isZero && !initialHeight.isZero else { @@ -1415,7 +1425,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - override func openMessageContextMenu() { + override public func openMessageContextMenu() { guard let item = self.item else { return } @@ -1423,7 +1433,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } private var absoluteRect: (CGRect, CGSize)? - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) var rect = rect @@ -1454,13 +1464,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let reactionButtonsNode = self.reactionButtonsNode { reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration) } } - override func targetReactionView(value: MessageReaction.Reaction) -> UIView? { + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result } @@ -1470,7 +1480,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD return nil } - override func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: StoryId) -> UIView? { guard let item = self.item else { return nil } @@ -1486,7 +1496,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD return nil } - override func contentFrame() -> CGRect { + override public func contentFrame() -> CGRect { return self.interactiveVideoNode.frame } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD new file mode 100644 index 0000000000..e7be503452 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/BUILD @@ -0,0 +1,49 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInteractiveFileNode", + module_name = "ChatMessageInteractiveFileNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/RadialStatusNode", + "//submodules/SemanticStatusNode", + "//submodules/FileMediaResourceStatus", + "//submodules/CheckNode", + "//submodules/MusicAlbumArtResources", + "//submodules/AudioBlob", + "//submodules/ContextUI", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AudioTranscriptionButtonComponent", + "//submodules/TelegramUI/Components/AudioWaveformComponent", + "//submodules/ShimmerEffect", + "//submodules/Media/ConvertOpusToAAC", + "//submodules/Media/LocalAudioTranscription", + "//submodules/TextSelectionNode", + "//submodules/TelegramUI/Components/AudioTranscriptionPendingIndicatorComponent", + "//submodules/UndoUI", + "//submodules/TelegramNotices", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 9c86bb318d..9cf63aaa82 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -38,52 +38,30 @@ private struct FetchControls { let cancel: () -> Void } -enum TranscribedText: Equatable { - case success(text: String, isPending: Bool) - case error(AudioTranscriptionMessageAttribute.TranscriptionError) -} - -func transcribedText(message: Message) -> TranscribedText? { - for attribute in message.attributes { - if let attribute = attribute as? AudioTranscriptionMessageAttribute { - if !attribute.text.isEmpty { - return .success(text: attribute.text, isPending: attribute.isPending) - } else { - if attribute.isPending { - return nil - } else { - return .error(attribute.error ?? .generic) - } - } - } - } - return nil -} - -final class ChatMessageInteractiveFileNode: ASDisplayNode { - final class Arguments { - let context: AccountContext - let presentationData: ChatPresentationData - let message: Message - let topMessage: Message - let associatedData: ChatMessageItemAssociatedData - let chatLocation: ChatLocation - let attributes: ChatMessageEntryAttributes - let isPinned: Bool - let forcedIsEdited: Bool - let file: TelegramMediaFile - let automaticDownload: Bool - let incoming: Bool - let isRecentActions: Bool - let forcedResourceStatus: FileMediaResourceStatus? - let dateAndStatusType: ChatMessageDateAndStatusType? - let displayReactions: Bool - let messageSelection: Bool? - let layoutConstants: ChatMessageItemLayoutConstants - let constrainedSize: CGSize - let controllerInteraction: ChatControllerInteraction +public final class ChatMessageInteractiveFileNode: ASDisplayNode { + public final class Arguments { + public let context: AccountContext + public let presentationData: ChatPresentationData + public let message: Message + public let topMessage: Message + public let associatedData: ChatMessageItemAssociatedData + public let chatLocation: ChatLocation + public let attributes: ChatMessageEntryAttributes + public let isPinned: Bool + public let forcedIsEdited: Bool + public let file: TelegramMediaFile + public let automaticDownload: Bool + public let incoming: Bool + public let isRecentActions: Bool + public let forcedResourceStatus: FileMediaResourceStatus? + public let dateAndStatusType: ChatMessageDateAndStatusType? + public let displayReactions: Bool + public let messageSelection: Bool? + public let layoutConstants: ChatMessageItemLayoutConstants + public let constrainedSize: CGSize + public let controllerInteraction: ChatControllerInteraction - init( + public init( context: AccountContext, presentationData: ChatPresentationData, message: Message, @@ -133,31 +111,25 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private let titleNode: TextNode private let descriptionNode: TextNode private let descriptionMeasuringNode: TextNode - let fetchingTextNode: ImmediateTextNode - let fetchingCompactTextNode: ImmediateTextNode + public let fetchingTextNode: ImmediateTextNode + public let fetchingCompactTextNode: ImmediateTextNode - var waveformView: ComponentHostView? + public var waveformView: ComponentHostView? - /*private let waveformNode: AudioWaveformNode - private let waveformForegroundNode: AudioWaveformNode - private var waveformShimmerNode: ShimmerEffectNode? - private var waveformMaskNode: AudioWaveformNode? - private var waveformScrubbingNode: MediaPlayerScrubbingNode?*/ - - var audioTranscriptionButton: ComponentHostView? + public var audioTranscriptionButton: ComponentHostView? private var transcriptionPendingIndicator: ComponentHostView? - let textNode: TextNode - let textClippingNode: ASDisplayNode + public let textNode: TextNode + public let textClippingNode: ASDisplayNode private var textSelectionNode: TextSelectionNode? - var updateIsTextSelectionActive: ((Bool) -> Void)? + public var updateIsTextSelectionActive: ((Bool) -> Void)? - let dateAndStatusNode: ChatMessageDateAndStatusNode + public let dateAndStatusNode: ChatMessageDateAndStatusNode private let consumableContentNode: ASImageNode private var iconNode: TransformImageNode? - let statusContainerNode: ContextExtractedContentContainingNode - var statusNode: SemanticStatusNode? + public let statusContainerNode: ContextExtractedContentContainingNode + public var statusNode: SemanticStatusNode? private var playbackAudioLevelNode: VoiceBlobNode? private var streamingStatusNode: SemanticStatusNode? private var tapRecognizer: UITapGestureRecognizer? @@ -185,7 +157,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var inputAudioLevel: CGFloat = 0.0 private var currentAudioLevel: CGFloat = 0.0 - var visibility: Bool = false { + public var visibility: Bool = false { didSet { guard self.visibility != oldValue else { return } @@ -200,12 +172,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var actualFetchStatus: MediaResourceStatus? private let fetchDisposable = MetaDisposable() - var toggleSelection: (Bool) -> Void = { _ in } - var activateLocalContent: () -> Void = { } - var requestUpdateLayout: (Bool) -> Void = { _ in } - var displayImportedTooltip: (ASDisplayNode) -> Void = { _ in } + public var toggleSelection: (Bool) -> Void = { _ in } + public var activateLocalContent: () -> Void = { } + public var requestUpdateLayout: (Bool) -> Void = { _ in } + public var displayImportedTooltip: (ASDisplayNode) -> Void = { _ in } - var updateTranscriptionExpanded: ((AudioTranscriptionButtonComponent.TranscriptionState) -> Void)? + public var updateTranscriptionExpanded: ((AudioTranscriptionButtonComponent.TranscriptionState) -> Void)? private var context: AccountContext? private var message: Message? @@ -216,10 +188,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var streamingCacheStatusFrame: CGRect? private var fileIconImage: UIImage? - var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed - var forcedAudioTranscriptionText: TranscribedText? + public var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed + public var forcedAudioTranscriptionText: TranscribedText? private var transcribeDisposable: Disposable? - var hasExpandedAudioTranscription: Bool { + public var hasExpandedAudioTranscription: Bool { if case .expanded = audioTranscriptionState { return true } else { @@ -230,7 +202,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var hapticFeedback: HapticFeedback? - override init() { + override public init() { self.titleNode = TextNode() self.titleNode.displaysAsynchronously = false self.titleNode.isUserInteractionEnabled = false @@ -293,13 +265,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.transcribeDisposable?.dispose() } - override func didLoad() { + override public func didLoad() { let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.fileTap(_:))) self.view.addGestureRecognizer(tapRecognizer) self.tapRecognizer = tapRecognizer } - @objc func cacheProgressPressed() { + @objc private func cacheProgressPressed() { guard let resourceStatus = self.resourceStatus else { return } @@ -317,7 +289,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - @objc func progressPressed() { + @objc private func progressPressed() { if let resourceStatus = self.resourceStatus { switch resourceStatus.mediaStatus { case let .fetchStatus(fetchStatus): @@ -345,7 +317,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - @objc func fileTap(_ recognizer: UITapGestureRecognizer) { + @objc private func fileTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { if let streamingCacheStatusFrame = self.streamingCacheStatusFrame, streamingCacheStatusFrame.contains(recognizer.location(in: self.view)) { self.cacheProgressPressed() @@ -491,7 +463,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - func asyncLayout() -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))) { + public func asyncLayout() -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))) { let currentFile = self.file let titleAsyncLayout = TextNode.asyncLayout(self.titleNode) @@ -1735,7 +1707,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize) } - static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode))) { + public static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode))) { let currentAsyncLayout = node?.asyncLayout() return { arguments in @@ -1767,7 +1739,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - func willUpdateIsExtractedToContextPreview(_ value: Bool) { + public func willUpdateIsExtractedToContextPreview(_ value: Bool) { if !value { if let textSelectionNode = self.textSelectionNode { self.textSelectionNode = nil @@ -1780,7 +1752,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - func updateIsExtractedToContextPreview(_ value: Bool) { + public func updateIsExtractedToContextPreview(_ value: Bool) { if value { if self.textSelectionNode == nil, self.textClippingNode.supernode != nil, let item = self.arguments, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() { let selectionColor: UIColor @@ -1805,7 +1777,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } item.controllerInteraction.performTextSelectionAction(item.message, true, text, action) }) - textSelectionNode.enableQuote = item.controllerInteraction.canSetupReply(item.message) == .reply + textSelectionNode.enableQuote = true self.textSelectionNode = textSelectionNode self.textClippingNode.addSubnode(textSelectionNode) self.textClippingNode.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode) @@ -1825,7 +1797,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let iconNode = self.iconNode, let file = self.file, file.isEqual(to: media) { return (iconNode, iconNode.bounds, { [weak iconNode] in return (iconNode?.view.snapshotContentTree(unhide: true), nil) @@ -1835,7 +1807,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - func updateHiddenMedia(_ media: [Media]?) -> Bool { + public func updateHiddenMedia(_ media: [Media]?) -> Bool { var isHidden = false if let file = self.file, let media = media { for m in media { @@ -1864,7 +1836,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.playerUpdateTimer = nil } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.dateAndStatusNode.supernode != nil { if let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) { return result @@ -1883,27 +1855,23 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { return super.hitTest(point, with: event) } - func hasTapAction(at point: CGPoint) -> Bool { + public func hasTapAction(at point: CGPoint) -> Bool { if let _ = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: nil) { return true } return false } - func animateSent() { + public func animateSent() { if let view = self.waveformView?.componentView as? AudioWaveformComponent.View { view.animateIn() } } - - func animateTo(_ node: ChatMessageInteractiveInstantVideoNode) { - - } } -final class FileMessageSelectionNode: ASDisplayNode { - enum NodeType { +public final class FileMessageSelectionNode: ASDisplayNode { + public enum NodeType { case media case file case voice diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD new file mode 100644 index 0000000000..d484c25a5f --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/BUILD @@ -0,0 +1,44 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInteractiveInstantVideoNode", + module_name = "ChatMessageInteractiveInstantVideoNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + #"-Xfrontend", "-debug-time-function-bodies" + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/RadialStatusNode", + "//submodules/PhotoResources", + "//submodules/TelegramUniversalVideoContent", + "//submodules/FileMediaResourceStatus", + "//submodules/Components/HierarchyTrackingLayer", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AudioTranscriptionButtonComponent", + "//submodules/UndoUI", + "//submodules/TelegramNotices", + "//submodules/Markdown", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", + "//submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift similarity index 90% rename from submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 720cc6df06..2f14bbbb65 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -24,14 +24,26 @@ import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageBubbleContentNode import ChatMessageReplyInfoNode +import InstantVideoRadialStatusNode +import ChatInstantVideoMessageDurationNode -struct ChatMessageInstantVideoItemLayoutResult { - let contentSize: CGSize - let overflowLeft: CGFloat - let overflowRight: CGFloat +public struct ChatMessageInstantVideoItemLayoutResult { + public let contentSize: CGSize + public let overflowLeft: CGFloat + public let overflowRight: CGFloat + + public init( + contentSize: CGSize, + overflowLeft: CGFloat, + overflowRight: CGFloat + ) { + self.contentSize = contentSize + self.overflowLeft = overflowLeft + self.overflowRight = overflowRight + } } -enum ChatMessageInstantVideoItemLayoutData { +public enum ChatMessageInstantVideoItemLayoutData { case unconstrained(width: CGFloat) case constrained(left: CGFloat, right: CGFloat) } @@ -41,12 +53,12 @@ private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont -enum ChatMessageInteractiveInstantVideoNodeStatusType { +public enum ChatMessageInteractiveInstantVideoNodeStatusType { case free case bubble } -class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { +public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private var hierarchyTrackingLayer: HierarchyTrackingLayer? private var trackingIsInHierarchy: Bool = false { didSet { @@ -58,7 +70,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - var canAttachContent: Bool = false { + public var canAttachContent: Bool = false { didSet { if self.canAttachContent != oldValue { Queue.mainQueue().justDispatch { @@ -72,32 +84,32 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private let secretVideoPlaceholderBackground: ASImageNode private let secretVideoPlaceholder: TransformImageNode - var audioTranscriptionButton: ComponentHostView? + public var audioTranscriptionButton: ComponentHostView? private var statusNode: RadialStatusNode? private var disappearingStatusNode: RadialStatusNode? private var playbackStatusNode: InstantVideoRadialStatusNode? - private(set) var videoFrame: CGRect? + public private(set) var videoFrame: CGRect? private var imageScale: CGFloat = 1.0 private var item: ChatMessageBubbleContentItem? private var automaticDownload: Bool? - var media: TelegramMediaFile? - var appliedForwardInfo: (Peer?, String?)? + public var media: TelegramMediaFile? + public var appliedForwardInfo: (Peer?, String?)? private let fetchDisposable = MetaDisposable() private var durationBackgroundNode: NavigationBackgroundNode? private var durationNode: ChatInstantVideoMessageDurationNode? - let dateAndStatusNode: ChatMessageDateAndStatusNode + public let dateAndStatusNode: ChatMessageDateAndStatusNode private let infoBackgroundNode: ASImageNode private let muteIconNode: ASImageNode - var viaBotNode: TextNode? - var replyInfoNode: ChatMessageReplyInfoNode? - var replyBackgroundNode: NavigationBackgroundNode? - var forwardInfoNode: ChatMessageForwardInfoNode? + public var viaBotNode: TextNode? + public var replyInfoNode: ChatMessageReplyInfoNode? + public var replyBackgroundNode: NavigationBackgroundNode? + public var forwardInfoNode: ChatMessageForwardInfoNode? private var status: FileMediaResourceStatus? private var playerStatus: MediaPlayerStatus? { @@ -119,7 +131,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - var visibility: Bool = false { + public var visibility: Bool = false { didSet { if self.visibility != oldValue { self.videoNode?.canAttachContent = self.shouldAcquireVideoContext @@ -127,15 +139,15 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - var shouldOpen: () -> Bool = { return true } + public var shouldOpen: () -> Bool = { return true } - var updateTranscriptionExpanded: ((AudioTranscriptionButtonComponent.TranscriptionState) -> Void)? - var updateTranscriptionText: ((TranscribedText?) -> Void)? + public var updateTranscriptionExpanded: ((AudioTranscriptionButtonComponent.TranscriptionState) -> Void)? + public var updateTranscriptionText: ((TranscribedText?) -> Void)? - var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed - var audioTranscriptionText: TranscribedText? + public var audioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState = .collapsed + public var audioTranscriptionText: TranscribedText? private var transcribeDisposable: Disposable? - var hasExpandedAudioTranscription: Bool { + public var hasExpandedAudioTranscription: Bool { if case .expanded = audioTranscriptionState { return true } else { @@ -146,9 +158,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private var hapticFeedback: HapticFeedback? - var requestUpdateLayout: (Bool) -> Void = { _ in } + public var requestUpdateLayout: (Bool) -> Void = { _ in } - override init() { + override public init() { self.secretVideoPlaceholderBackground = ASImageNode() self.secretVideoPlaceholderBackground.isLayerBacked = true self.secretVideoPlaceholderBackground.displaysAsynchronously = false @@ -174,7 +186,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.infoBackgroundNode.addSubnode(self.muteIconNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -185,7 +197,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.fetchedThumbnailDisposable.dispose() } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -212,7 +224,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.layer.addSublayer(hierarchyTrackingLayer) } - func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool, _ avatarInset: CGFloat) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ListViewItemUpdateAnimation) -> Void) { + public func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool, _ avatarInset: CGFloat) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ListViewItemUpdateAnimation) -> Void) { let previousFile = self.media let currentItem = self.item @@ -235,7 +247,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { var updatedMuteIconImage: UIImage? var incoming = item.message.effectivelyIncoming(item.context.account.peerId) - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } @@ -312,8 +324,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let bubbleEdgeInset: CGFloat = 4.0 let bubbleContentInsetsLeft: CGFloat = 6.0 - let availableWidth = max(60.0, width - 210.0 - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0) - let availableContentWidth = width - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0 + let availableWidth: CGFloat = max(60.0, width - 210.0 - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0) + let availableContentWidth: CGFloat = width - bubbleEdgeInset * 2.0 - bubbleContentInsetsLeft - 20.0 if !ignoreHeaders { var replyMessage: Message? @@ -418,7 +430,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { forwardAuthorSignature = forwardInfo.authorSignature } } - let availableWidth = max(60.0, availableContentWidth - 210.0 + 6.0) + let availableWidth: CGFloat = max(60.0, availableContentWidth - 210.0 + 6.0) forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } @@ -1242,7 +1254,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { @@ -1352,12 +1364,12 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let audioTranscriptionButton = self.audioTranscriptionButton, !audioTranscriptionButton.isHidden, audioTranscriptionButton.frame.contains(point) { return audioTranscriptionButton } if let playbackNode = self.playbackStatusNode, !self.isPlaying, !playbackNode.frame.insetBy(dx: 0.2 * playbackNode.frame.width, dy: 0.2 * playbackNode.frame.height).contains(point) { - let distanceFromCenter = point.distanceTo(playbackNode.position) + let distanceFromCenter = sqrt(pow(point.x - playbackNode.position.x, 2.0) + pow(point.y - playbackNode.position.y, 2.0)) if distanceFromCenter < 0.2 * playbackNode.frame.width { return self.view } else { @@ -1413,12 +1425,12 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { self.progressPressed() return true } - func videoContentNode(at point: CGPoint) -> ASDisplayNode? { + public func videoContentNode(at point: CGPoint) -> ASDisplayNode? { if let videoFrame = self.videoFrame { if videoFrame.contains(point) { return self.videoNode @@ -1427,7 +1439,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { return nil } - static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool, _ avatarInset: CGFloat) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ListViewItemUpdateAnimation) -> ChatMessageInteractiveInstantVideoNode) { + public static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool, _ avatarInset: CGFloat) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ListViewItemUpdateAnimation) -> ChatMessageInteractiveInstantVideoNode) { let makeLayout = node?.asyncLayout() return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload, avatarInset in var createdNode: ChatMessageInteractiveInstantVideoNode? @@ -1450,7 +1462,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - var isPlaying: Bool { + public var isPlaying: Bool { if let status = self.status, case let .playbackStatus(playbackStatus) = status.mediaStatus, case .playing = playbackStatus { return true } else { @@ -1458,21 +1470,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - func seekTo(_ position: Double) { + public func seekTo(_ position: Double) { if let duration = self.playbackStatusNode?.duration { self.videoNode?.seek(position * duration) } } - func play() { + public func play() { self.videoNode?.play() } - func pause() { + public func pause() { self.videoNode?.pause() } - func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? { + public func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? { if let item = self.item { var isUnconsumed = false for attribute in item.message.attributes { @@ -1512,7 +1524,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } private var animatedFadeIn = false - func animateFromSnapshot(snapshotView: UIView, transition: CombinedTransition) { + public func animateFromSnapshot(snapshotView: UIView, transition: CombinedTransition) { guard let videoFrame = self.videoFrame else { return } @@ -1621,20 +1633,40 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.updateTranscriptionExpanded?(self.audioTranscriptionState) } - func animateTo(_ node: ChatMessageInteractiveFileNode, animator: ControlledTransitionAnimator) { + public final class AnimateFileNodeDescription { + public let node: ASDisplayNode + public let textClippingNode: ASDisplayNode + public let dateAndStatusNode: ASDisplayNode + public let fetchingTextNode: ASDisplayNode + public let waveformView: UIView? + public let statusNode: ASDisplayNode? + public let audioTranscriptionButton: UIView? + + public init(node: ASDisplayNode, textClippingNode: ASDisplayNode, dateAndStatusNode: ASDisplayNode, fetchingTextNode: ASDisplayNode, waveformView: UIView?, statusNode: ASDisplayNode?, audioTranscriptionButton: UIView?) { + self.node = node + self.textClippingNode = textClippingNode + self.dateAndStatusNode = dateAndStatusNode + self.fetchingTextNode = fetchingTextNode + self.waveformView = waveformView + self.statusNode = statusNode + self.audioTranscriptionButton = audioTranscriptionButton + } + } + + public func animateTo(_ animateToFile: AnimateFileNodeDescription, animator: ControlledTransitionAnimator) { let duration: Double = 0.2 - node.alpha = 1.0 - if node.supernode == nil { - self.supernode?.insertSubnode(node, belowSubnode: self) + animateToFile.node.alpha = 1.0 + if animateToFile.node.supernode == nil { + self.supernode?.insertSubnode(animateToFile.node, belowSubnode: self) } self.alpha = 0.0 self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) - node.waveformView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1) + animateToFile.waveformView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.1) - if let videoNode = self.videoNode, let targetNode = node.statusNode, let videoSnapshotView = videoNode.view.snapshotView(afterScreenUpdates: false) { + if let videoNode = self.videoNode, let targetNode = animateToFile.statusNode, let videoSnapshotView = videoNode.view.snapshotView(afterScreenUpdates: false) { videoSnapshotView.frame = videoNode.bounds videoNode.view.insertSubview(videoSnapshotView, at: 1) videoSnapshotView.alpha = 0.0 @@ -1660,32 +1692,33 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { playbackStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) } - let sourceFrame = self.view.convert(videoNode.frame, to: node.view) + let sourceFrame = self.view.convert(videoNode.frame, to: animateToFile.node.view) animator.animatePosition(layer: targetNode.layer, from: sourceFrame.center, to: targetNode.position, completion: nil) let sourceScale = (videoNode.bounds.width * self.imageScale) / targetNode.frame.width animator.animateScale(layer: targetNode.layer, from: sourceScale, to: 1.0, completion: nil) targetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) let verticalDelta = (videoNode.position.y - targetFrame.center.y) * 2.0 - animator.animatePosition(layer: node.textClippingNode.layer, from: node.textClippingNode.position.offsetBy(dx: 0.0, dy: verticalDelta), to: node.textClippingNode.position, completion: nil) - node.textClippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) + animator.animatePosition(layer: animateToFile.textClippingNode.layer, from: animateToFile.textClippingNode.position.offsetBy(dx: 0.0, dy: verticalDelta), to: animateToFile.textClippingNode.position, completion: nil) + animateToFile.textClippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) if let maskImage = generateGradientImage(size: CGSize(width: 8.0, height: 10.0), colors: [UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 1.0], direction: .vertical) { - let textClippingFrame = node.textClippingNode.frame + let textClippingFrame = animateToFile.textClippingNode.frame let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)) - node.textClippingNode.view.mask = maskView + animateToFile.textClippingNode.view.mask = maskView maskView.frame = CGRect(origin: CGPoint(), size: CGSize(width: textClippingFrame.width, height: maskImage.size.height)) - animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: textClippingFrame.size), completion: { [weak maskView, weak node] _ in + let nodeTextClippingNode = animateToFile.textClippingNode + animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: textClippingFrame.size), completion: { [weak maskView, weak nodeTextClippingNode] _ in maskView?.removeFromSuperview() - node?.textClippingNode.view.mask = nil + nodeTextClippingNode?.view.mask = nil }) } } - if let audioTranscriptionButton = self.audioTranscriptionButton, let targetAudioTranscriptionButton = node.audioTranscriptionButton { - let sourceFrame = audioTranscriptionButton.convert(audioTranscriptionButton.bounds, to: node.view) + if let audioTranscriptionButton = self.audioTranscriptionButton, let targetAudioTranscriptionButton = animateToFile.audioTranscriptionButton { + let sourceFrame = audioTranscriptionButton.convert(audioTranscriptionButton.bounds, to: animateToFile.node.view) animator.animatePosition(layer: targetAudioTranscriptionButton.layer, from: sourceFrame.center, to: targetAudioTranscriptionButton.center, completion: nil) targetAudioTranscriptionButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) @@ -1695,28 +1728,28 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { audioTranscriptionButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) } - let sourceDateFrame = self.dateAndStatusNode.view.convert(self.dateAndStatusNode.view.bounds, to: node.view) - let targetDateFrame = node.dateAndStatusNode.view.convert(node.dateAndStatusNode.view.bounds, to: self.view) + let sourceDateFrame = self.dateAndStatusNode.view.convert(self.dateAndStatusNode.view.bounds, to: animateToFile.node.view) + let targetDateFrame = animateToFile.dateAndStatusNode.view.convert(animateToFile.dateAndStatusNode.view.bounds, to: self.view) animator.animatePosition(layer: self.dateAndStatusNode.layer, from: self.dateAndStatusNode.position, to: CGPoint(x: targetDateFrame.maxX - self.dateAndStatusNode.frame.width / 2.0 + 2.0, y: targetDateFrame.midY - 7.0), completion: nil) - animator.animatePosition(layer: node.dateAndStatusNode.layer, from: CGPoint(x: sourceDateFrame.maxX - node.dateAndStatusNode.frame.width / 2.0, y: sourceDateFrame.midY + 7.0), to: node.dateAndStatusNode.position, completion: nil) + animator.animatePosition(layer: animateToFile.dateAndStatusNode.layer, from: CGPoint(x: sourceDateFrame.maxX - animateToFile.dateAndStatusNode.frame.width / 2.0, y: sourceDateFrame.midY + 7.0), to: animateToFile.dateAndStatusNode.position, completion: nil) self.dateAndStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) - node.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration - 0.05, delay: 0.05) + animateToFile.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration - 0.05, delay: 0.05) if let durationNode = self.durationNode, let durationBackgroundNode = self.durationBackgroundNode { - let sourceDurationFrame = durationNode.view.convert(durationNode.view.bounds, to: node.view) - let targetDurationFrame = node.fetchingTextNode.view.convert(node.fetchingTextNode.view.bounds, to: self.view) + let sourceDurationFrame = durationNode.view.convert(durationNode.view.bounds, to: animateToFile.node.view) + let targetDurationFrame = animateToFile.fetchingTextNode.view.convert(animateToFile.fetchingTextNode.view.bounds, to: self.view) let delta = CGPoint(x: targetDurationFrame.center.x - durationNode.position.x, y: targetDurationFrame.center.y - durationNode.position.y) animator.animatePosition(layer: durationNode.layer, from: durationNode.position, to: targetDurationFrame.center, completion: nil) animator.animatePosition(layer: durationBackgroundNode.layer, from: durationBackgroundNode.position, to: durationBackgroundNode.position.offsetBy(dx: delta.x, dy: delta.y), completion: nil) - animator.animatePosition(layer: node.fetchingTextNode.layer, from: sourceDurationFrame.center, to: node.fetchingTextNode.position, completion: nil) + animator.animatePosition(layer: animateToFile.fetchingTextNode.layer, from: sourceDurationFrame.center, to: animateToFile.fetchingTextNode.position, completion: nil) durationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) self.durationBackgroundNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) - node.fetchingTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration - 0.05, delay: 0.05) + animateToFile.fetchingTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration - 0.05, delay: 0.05) } if let viaBotNode = self.viaBotNode { @@ -1733,19 +1766,20 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - func animateFrom(_ node: ChatMessageInteractiveFileNode, animator: ControlledTransitionAnimator) { + public func animateFrom(_ animateFromFile: AnimateFileNodeDescription, animator: ControlledTransitionAnimator) { let duration: Double = 0.2 self.alpha = 1.0 self.isHidden = false - node.alpha = 0.0 - node.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { _ in - node.removeFromSupernode() + animateFromFile.node.alpha = 0.0 + let animateToFileNode = animateFromFile.node + animateFromFile.node.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak animateToFileNode] _ in + animateToFileNode?.removeFromSupernode() }) - node.waveformView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) + animateFromFile.waveformView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) - if let videoNode = self.videoNode, let sourceNode = node.statusNode { + if let videoNode = self.videoNode, let sourceNode = animateFromFile.statusNode { videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) @@ -1763,34 +1797,35 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { playbackStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) } - let targetFrame = self.view.convert(videoNode.frame, to: node.view) + let targetFrame = self.view.convert(videoNode.frame, to: animateFromFile.node.view) animator.animatePosition(layer: sourceNode.layer, from: sourceNode.position, to: targetFrame.center, completion: nil) let targetScale = (videoNode.bounds.width * self.imageScale) / sourceNode.frame.width animator.animateScale(layer: sourceNode.layer, from: 1.0, to: targetScale, completion: nil) sourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) let verticalDelta = (videoNode.position.y - sourceFrame.center.y) * 2.0 - animator.animatePosition(layer: node.textClippingNode.layer, from: node.textClippingNode.position, to: node.textClippingNode.position.offsetBy(dx: 0.0, dy: verticalDelta), completion: nil) - node.textClippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) + animator.animatePosition(layer: animateFromFile.textClippingNode.layer, from: animateFromFile.textClippingNode.position, to: animateFromFile.textClippingNode.position.offsetBy(dx: 0.0, dy: verticalDelta), completion: nil) + animateFromFile.textClippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) if let maskImage = generateGradientImage(size: CGSize(width: 8.0, height: 10.0), colors: [UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 1.0], direction: .vertical) { - let textClippingFrame = node.textClippingNode.frame + let textClippingFrame = animateFromFile.textClippingNode.frame let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)) - node.textClippingNode.view.mask = maskView + animateFromFile.textClippingNode.view.mask = maskView maskView.frame = CGRect(origin: CGPoint(), size: textClippingFrame.size) - animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: textClippingFrame.width, height: maskImage.size.height)), completion: { [weak maskView, weak node] _ in + let animateFromFileTextClippingNode = animateFromFile.textClippingNode + animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: textClippingFrame.width, height: maskImage.size.height)), completion: { [weak maskView, weak animateFromFileTextClippingNode] _ in maskView?.removeFromSuperview() - node?.textClippingNode.view.mask = nil + animateFromFileTextClippingNode?.view.mask = nil }) } } - if let audioTranscriptionButton = self.audioTranscriptionButton, let sourceAudioTranscriptionButton = node.audioTranscriptionButton { + if let audioTranscriptionButton = self.audioTranscriptionButton, let sourceAudioTranscriptionButton = animateFromFile.audioTranscriptionButton { audioTranscriptionButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) - let targetFrame = audioTranscriptionButton.convert(audioTranscriptionButton.bounds, to: node.view) + let targetFrame = audioTranscriptionButton.convert(audioTranscriptionButton.bounds, to: animateFromFile.node.view) animator.animatePosition(layer: sourceAudioTranscriptionButton.layer, from: sourceAudioTranscriptionButton.center, to: targetFrame.center, completion: nil) sourceAudioTranscriptionButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) @@ -1798,28 +1833,28 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { animator.animatePosition(layer: audioTranscriptionButton.layer, from: sourceFrame.center, to: audioTranscriptionButton.center, completion: nil) } - let sourceDateFrame = node.dateAndStatusNode.view.convert(node.dateAndStatusNode.view.bounds, to: self.view) - let targetDateFrame = self.dateAndStatusNode.view.convert(self.dateAndStatusNode.view.bounds, to: node.view) + let sourceDateFrame = animateFromFile.dateAndStatusNode.view.convert(animateFromFile.dateAndStatusNode.view.bounds, to: self.view) + let targetDateFrame = self.dateAndStatusNode.view.convert(self.dateAndStatusNode.view.bounds, to: animateFromFile.node.view) animator.animatePosition(layer: self.dateAndStatusNode.layer, from: CGPoint(x: sourceDateFrame.maxX - self.dateAndStatusNode.frame.width / 2.0 + 2.0, y: sourceDateFrame.midY - 7.0), to: self.dateAndStatusNode.position, completion: nil) - animator.animatePosition(layer: node.dateAndStatusNode.layer, from: node.dateAndStatusNode.position, to: CGPoint(x: targetDateFrame.maxX - node.dateAndStatusNode.frame.width / 2.0, y: targetDateFrame.midY + 7.0), completion: nil) + animator.animatePosition(layer: animateFromFile.dateAndStatusNode.layer, from: animateFromFile.dateAndStatusNode.position, to: CGPoint(x: targetDateFrame.maxX - animateFromFile.dateAndStatusNode.frame.width / 2.0, y: targetDateFrame.midY + 7.0), completion: nil) self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) - node.dateAndStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) + animateFromFile.dateAndStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) if let durationNode = self.durationNode, let durationBackgroundNode = self.durationBackgroundNode { - let sourceDurationFrame = node.fetchingTextNode.view.convert(node.fetchingTextNode.view.bounds, to: self.view) - let targetDurationFrame = durationNode.view.convert(durationNode.view.bounds, to: node.view) + let sourceDurationFrame = animateFromFile.fetchingTextNode.view.convert(animateFromFile.fetchingTextNode.view.bounds, to: self.view) + let targetDurationFrame = durationNode.view.convert(durationNode.view.bounds, to: animateFromFile.node.view) let delta = CGPoint(x: sourceDurationFrame.center.x - durationNode.position.x, y: sourceDurationFrame.center.y - durationNode.position.y) animator.animatePosition(layer: durationNode.layer, from: sourceDurationFrame.center, to: durationNode.position, completion: nil) animator.animatePosition(layer: durationBackgroundNode.layer, from: durationBackgroundNode.position.offsetBy(dx: delta.x, dy: delta.y), to: durationBackgroundNode.position, completion: nil) - animator.animatePosition(layer: node.fetchingTextNode.layer, from: node.fetchingTextNode.position, to: targetDurationFrame.center, completion: nil) + animator.animatePosition(layer: animateFromFile.fetchingTextNode.layer, from: animateFromFile.fetchingTextNode.position, to: targetDurationFrame.center, completion: nil) durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration - 0.05, delay: 0.05) - node.fetchingTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) + animateFromFile.fetchingTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration) } if let viaBotNode = self.viaBotNode { @@ -1838,7 +1873,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.canAttachContent = false } - func targetForStoryTransition(id: StoryId) -> UIView? { + public func targetForStoryTransition(id: StoryId) -> UIView? { guard let item = self.item else { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD new file mode 100644 index 0000000000..1e1cb0ffcd --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD @@ -0,0 +1,46 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInteractiveMediaNode", + module_name = "ChatMessageInteractiveMediaNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/RadialStatusNode", + "//submodules/StickerResources", + "//submodules/PhotoResources", + "//submodules/TelegramUniversalVideoContent", + "//submodules/TelegramStringFormatting", + "//submodules/GalleryUI", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/LocalMediaResources", + "//submodules/WallpaperResources", + "//submodules/ChatMessageInteractiveMediaBadge", + "//submodules/ContextUI", + "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/WallpaperPreviewMedia", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift similarity index 97% rename from submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 576446ac90..330aed1c88 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -28,22 +28,23 @@ import StoryContainerScreen import ChatMessageDateAndStatusNode import ChatHistoryEntry import ChatMessageItemCommon +import WallpaperPreviewMedia private struct FetchControls { let fetch: (Bool) -> Void let cancel: () -> Void } -enum InteractiveMediaNodeSizeCalculation { +public enum InteractiveMediaNodeSizeCalculation { case constrained(CGSize) case unconstrained } -enum InteractiveMediaNodeContentMode { +public enum InteractiveMediaNodeContentMode { case aspectFit case aspectFill - var bubbleVideoDecorationContentMode: ChatBubbleVideoDecorationContentMode { + public var bubbleVideoDecorationContentMode: ChatBubbleVideoDecorationContentMode { switch self { case .aspectFit: return .aspectFit @@ -53,32 +54,52 @@ enum InteractiveMediaNodeContentMode { } } -enum InteractiveMediaNodeActivateContent { +public enum InteractiveMediaNodeActivateContent { case `default` case stream case automaticPlayback } -enum InteractiveMediaNodeAutodownloadMode { +public enum InteractiveMediaNodeAutodownloadMode { case none case prefetch case full } -enum InteractiveMediaNodePlayWithSoundMode { +public enum InteractiveMediaNodePlayWithSoundMode { case single case loop } -struct ChatMessageDateAndStatus { - var type: ChatMessageDateAndStatusType - var edited: Bool - var viewCount: Int? - var dateReactions: [MessageReaction] - var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)] - var dateReplies: Int - var isPinned: Bool - var dateText: String +public struct ChatMessageDateAndStatus { + public var type: ChatMessageDateAndStatusType + public var edited: Bool + public var viewCount: Int? + public var dateReactions: [MessageReaction] + public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)] + public var dateReplies: Int + public var isPinned: Bool + public var dateText: String + + public init( + type: ChatMessageDateAndStatusType, + edited: Bool, + viewCount: Int?, + dateReactions: [MessageReaction], + dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)], + dateReplies: Int, + isPinned: Bool, + dateText: String + ) { + self.type = type + self.edited = edited + self.viewCount = viewCount + self.dateReactions = dateReactions + self.dateReactionPeers = dateReactionPeers + self.dateReplies = dateReplies + self.isPinned = isPinned + self.dateText = dateText + } } public func roundedRectCgPath(roundRect rect: CGRect, topLeftRadius: CGFloat = 0.0, topRightRadius: CGFloat = 0.0, bottomLeftRadius: CGFloat = 0.0, bottomRightRadius: CGFloat = 0.0) -> CGPath { @@ -348,7 +369,7 @@ private class ExtendedMediaOverlayNode: ASDisplayNode { } } -final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitionNode { +public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitionNode { private let pinchContainerNode: PinchSourceContainerNode private let imageNode: TransformImageNode private var currentImageArguments: TransformImageArguments? @@ -360,11 +381,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio private var videoContent: NativeVideoContent? private var animatedStickerNode: AnimatedStickerNode? private var statusNode: RadialStatusNode? - var videoNodeDecoration: ChatBubbleVideoDecoration? - var decoration: UniversalVideoDecoration? { + public var videoNodeDecoration: ChatBubbleVideoDecoration? + public var decoration: UniversalVideoDecoration? { return self.videoNodeDecoration } - let dateAndStatusNode: ChatMessageDateAndStatusNode + public let dateAndStatusNode: ChatMessageDateAndStatusNode private var badgeNode: ChatMessageInteractiveMediaBadge? private var extendedMediaOverlayNode: ExtendedMediaOverlayNode? @@ -377,7 +398,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio private var sizeCalculation: InteractiveMediaNodeSizeCalculation? private var wideLayout: Bool? private var automaticDownload: InteractiveMediaNodeAutodownloadMode? - var automaticPlayback: Bool? + public var automaticPlayback: Bool? private let statusDisposable = MetaDisposable() private let fetchControls = Atomic(value: nil) @@ -404,8 +425,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio private var secretTimer: SwiftSignalKit.Timer? - var visibilityPromise = ValuePromise(false, ignoreRepeated: true) - var visibility: Bool = false { + public var visibilityPromise = ValuePromise(false, ignoreRepeated: true) + public var visibility: Bool = false { didSet { self.updateVisibility() } @@ -431,11 +452,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio self.visibilityPromise.set(visibility) } - var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in } - var activatePinch: ((PinchSourceContainerNode) -> Void)? - var updateMessageReaction: ((Message, ChatControllerInteractionReaction) -> Void)? + public var activateLocalContent: (InteractiveMediaNodeActivateContent) -> Void = { _ in } + public var activatePinch: ((PinchSourceContainerNode) -> Void)? + public var updateMessageReaction: ((Message, ChatControllerInteractionReaction) -> Void)? - override init() { + override public init() { self.pinchContainerNode = PinchSourceContainerNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode() @@ -541,15 +562,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio self.secretTimer?.invalidate() } - func isAvailableForGalleryTransition() -> Bool { + public func isAvailableForGalleryTransition() -> Bool { return self.automaticPlayback ?? false } - func isAvailableForInstantPageTransition() -> Bool { + public func isAvailableForInstantPageTransition() -> Bool { return false } - override func didLoad() { + override public func didLoad() { super.didLoad() self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageTap(_:)))) @@ -615,7 +636,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - @objc func imageTap(_ recognizer: UITapGestureRecognizer) { + @objc private func imageTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { let point = recognizer.location(in: self.imageNode.view) if let _ = self.attributes?.updatingMedia { @@ -660,7 +681,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { + public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { let currentMessage = self.message let currentMedia = self.media let imageLayout = self.imageNode.asyncLayout() @@ -2200,7 +2221,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))) { + public static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))) { let currentAsyncLayout = node?.asyncLayout() return { context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext in @@ -2232,11 +2253,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - func setOverlayColor(_ color: UIColor?, animated: Bool) { + public func setOverlayColor(_ color: UIColor?, animated: Bool) { self.imageNode.setOverlayColor(color, animated: animated) } - func isReadyForInteractivePreview() -> Bool { + public func isReadyForInteractivePreview() -> Bool { if let fetchStatus = self.fetchStatus, case .Local = fetchStatus { return true } else { @@ -2244,7 +2265,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - func updateIsHidden(_ isHidden: Bool) { + public func updateIsHidden(_ isHidden: Bool) { if isHidden && !self.internallyVisible { self.internallyVisible = true self.updateVisibility() @@ -2278,7 +2299,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } } - func transitionNode(adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { var bounds: CGRect if let currentImageArguments = self.currentImageArguments { if adjustRect { @@ -2336,7 +2357,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio }) } - func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? { + public func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? { var isAnimated = false if let file = self.media as? TelegramMediaFile, file.isAnimated { isAnimated = true diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD new file mode 100644 index 0000000000..78df073283 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInvoiceBubbleContentNode", + module_name = "ChatMessageInvoiceBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramStringFormatting", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift similarity index 77% rename from submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift index a899b479c6..57e4c691ab 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -9,22 +9,23 @@ import TelegramUIPreferences import TelegramStringFormatting import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentNode private let titleFont: UIFont = Font.semibold(15.0) private let textFont: UIFont = Font.regular(15.0) -final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { private var invoice: TelegramMediaInvoice? private let contentNode: ChatMessageAttachedContentNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.contentNode.visibility = self.visibility } } - required init() { + required public init() { self.contentNode = ChatMessageAttachedContentNode() super.init() @@ -32,11 +33,11 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.contentNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let contentNodeLayout = self.contentNode.asyncLayout() return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -100,23 +101,23 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { /*if let webPage = self.webPage, case let .Loaded(content) = webPage.content { if content.instantPage != nil { @@ -127,18 +128,18 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { return .none } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } return self.contentNode.transitionNode(media: media) } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.contentNode.statusNode.isHidden { return self.contentNode.statusNode.reactionView(value: value) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD new file mode 100644 index 0000000000..8c82069452 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageItem", + module_name = "ChatMessageItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift new file mode 100644 index 0000000000..e1356d27ce --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift @@ -0,0 +1,171 @@ +import Foundation +import UIKit +import Postbox +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramCore +import AccountContext +import ChatHistoryEntry +import ChatControllerInteraction +import TelegramPresentationData +import ChatMessageItemCommon + +public enum ChatMessageItemContent: Sequence { + case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: MessageHistoryEntryLocation?) + case group(messages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)]) + + public func effectivelyIncoming(_ accountPeerId: PeerId, associatedData: ChatMessageItemAssociatedData? = nil) -> Bool { + if let subject = associatedData?.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { + return false + } + switch self { + case let .message(message, _, _, _, _): + return message.effectivelyIncoming(accountPeerId) + case let .group(messages): + return messages[0].0.effectivelyIncoming(accountPeerId) + } + } + + public var index: MessageIndex { + switch self { + case let .message(message, _, _, _, _): + return message.index + case let .group(messages): + return messages[0].0.index + } + } + + public var firstMessage: Message { + switch self { + case let .message(message, _, _, _, _): + return message + case let .group(messages): + return messages[0].0 + } + } + + public var firstMessageAttributes: ChatMessageEntryAttributes { + switch self { + case let .message(_, _, _, attributes, _): + return attributes + case let .group(messages): + return messages[0].3 + } + } + + public func makeIterator() -> AnyIterator<(Message, ChatMessageEntryAttributes)> { + var index = 0 + return AnyIterator { () -> (Message, ChatMessageEntryAttributes)? in + switch self { + case let .message(message, _, _, attributes, _): + if index == 0 { + index += 1 + return (message, attributes) + } else { + index += 1 + return nil + } + case let .group(messages): + if index < messages.count { + let currentIndex = index + index += 1 + return (messages[currentIndex].0, messages[currentIndex].3) + } else { + return nil + } + } + } + } +} + +public enum ChatMessageItemAdditionalContent { + case eventLogPreviousMessage(Message) + case eventLogPreviousDescription(Message) + case eventLogPreviousLink(Message) +} + +public enum ChatMessageMerge: Int32 { + case none = 0 + case fullyMerged = 1 + case semanticallyMerged = 2 + + public var merged: Bool { + if case .none = self { + return false + } else { + return true + } + } +} + +public protocol ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { + func updateSelectionState(animated: Bool) + func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint) +} + +public protocol ChatMessageItem: ListViewItem { + var presentationData: ChatPresentationData { get } + var context: AccountContext { get } + var chatLocation: ChatLocation { get } + var associatedData: ChatMessageItemAssociatedData { get } + var controllerInteraction: ChatControllerInteraction { get } + var content: ChatMessageItemContent { get } + var disableDate: Bool { get } + var effectiveAuthorId: PeerId? { get } + var additionalContent: ChatMessageItemAdditionalContent? { get } + + var headers: [ListViewItemHeader] { get } + + var message: Message { get } + var read: Bool { get } + var unsent: Bool { get } + var sending: Bool { get } + var failed: Bool { get } + + func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) +} + +public func hasCommentButton(item: ChatMessageItem) -> Bool { + let firstMessage = item.content.firstMessage + + var hasDiscussion = false + if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { + hasDiscussion = true + } + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id { + hasDiscussion = false + } + + if firstMessage.adAttribute != nil { + hasDiscussion = false + } + + if hasDiscussion { + var canComment = false + if case .pinnedMessages = item.associatedData.subject { + canComment = false + } else if firstMessage.id.namespace == Namespaces.Message.Local { + canComment = true + } else { + for attribute in firstMessage.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId { + switch item.associatedData.channelDiscussionGroup { + case .unknown: + canComment = true + case let .known(groupId): + canComment = groupId == commentsPeerId + } + break + } + } + } + + if canComment { + return true + } + } else if firstMessage.id.peerId.isReplies { + return true + } + return false +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD index 527d1deaaf..0d4b817002 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display", "//submodules/Postbox", "//submodules/TelegramCore", + "//submodules/Emoji", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index 6c24b99597..3f7e6f0f67 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -3,6 +3,7 @@ import UIKit import Display import Postbox import TelegramCore +import Emoji public struct ChatMessageItemWidthFill { public var compactInset: CGFloat @@ -200,3 +201,98 @@ public let chatMessagePeerIdColors: [UIColor] = [ UIColor(rgb: 0x3ca5ec), UIColor(rgb: 0x3d72ed) ] + +public enum TranscribedText: Equatable { + case success(text: String, isPending: Bool) + case error(AudioTranscriptionMessageAttribute.TranscriptionError) +} + +public func transcribedText(message: Message) -> TranscribedText? { + for attribute in message.attributes { + if let attribute = attribute as? AudioTranscriptionMessageAttribute { + if !attribute.text.isEmpty { + return .success(text: attribute.text, isPending: attribute.isPending) + } else { + if attribute.isPending { + return nil + } else { + return .error(attribute.error ?? .generic) + } + } + } + } + return nil +} + +public func isPollEffectivelyClosed(message: Message, poll: TelegramMediaPoll) -> Bool { + if poll.isClosed { + return true + } else { + return false + } +} + +public extension ChatReplyThreadMessage { + var effectiveTopId: MessageId { + return self.channelMessageId ?? self.messageId + } +} + +public func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool { + if !message.text.isEmpty && message.text.containsOnlyEmoji { + if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) { + return false + } + return true + } else { + return false + } +} + +public func messageIsElligibleForLargeCustomEmoji(_ message: Message) -> Bool { + let text = message.text.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "") + guard !text.isEmpty && text.containsOnlyEmoji else { + return false + } + let entities = message.textEntitiesAttribute?.entities ?? [] + guard entities.count > 0 else { + return false + } + for entity in entities { + if case let .CustomEmoji(_, fileId) = entity.type { + if let _ = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { + + } else { + return false + } + } else { + return false + } + } + return true +} + +public func canAddMessageReactions(message: Message) -> Bool { + if message.id.namespace != Namespaces.Message.Cloud { + return false + } + if let peer = message.peers[message.id.peerId] { + if let _ = peer as? TelegramSecretChat { + return false + } + } else { + return false + } + for media in message.media { + if let _ = media as? TelegramMediaAction { + return false + } else if let story = media as? TelegramMediaStory { + if story.isMention { + return false + } + } else if let _ = media as? TelegramMediaExpiredContent { + return false + } + } + return true +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD new file mode 100644 index 0000000000..e704fe5675 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD @@ -0,0 +1,42 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageItemImpl", + module_name = "ChatMessageItemImpl", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AccountContext", + "//submodules/Emoji", + "//submodules/PersistentStringHash", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode", + "//submodules/AvatarNode", + "//submodules/TelegramUniversalVideoContent", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/GalleryUI", + "//submodules/Components/HierarchyTrackingLayer", + "//submodules/WallpaperBackgroundNode", + "//submodules/AvatarVideoNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatMessageDateHeader.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index c48afe8e2b..9253684a1f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -15,6 +15,7 @@ import HierarchyTrackingLayer import WallpaperBackgroundNode import ChatControllerInteraction import AvatarVideoNode +import ChatMessageItem private let timezoneOffset: Int32 = { let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) @@ -26,18 +27,18 @@ private let timezoneOffset: Int32 = { private let granularity: Int32 = 60 * 60 * 24 -final class ChatMessageDateHeader: ListViewItemHeader { +public final class ChatMessageDateHeader: ListViewItemHeader { private let timestamp: Int32 private let roundedTimestamp: Int32 private let scheduled: Bool - let id: ListViewItemNode.HeaderId - let presentationData: ChatPresentationData - let controllerInteraction: ChatControllerInteraction? - let context: AccountContext - let action: ((Int32, Bool) -> Void)? + public let id: ListViewItemNode.HeaderId + public let presentationData: ChatPresentationData + public let controllerInteraction: ChatControllerInteraction? + public let context: AccountContext + public let action: ((Int32, Bool) -> Void)? - init(timestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { + public init(timestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { self.timestamp = timestamp self.scheduled = scheduled self.presentationData = presentationData @@ -48,10 +49,10 @@ final class ChatMessageDateHeader: ListViewItemHeader { self.id = ListViewItemNode.HeaderId(space: 0, id: Int64(self.roundedTimestamp)) } - let stickDirection: ListViewItemHeaderStickDirection = .bottom - let stickOverInsets: Bool = true + public let stickDirection: ListViewItemHeaderStickDirection = .bottom + public let stickOverInsets: Bool = true - let height: CGFloat = 34.0 + public let height: CGFloat = 34.0 public func combinesWith(other: ListViewItemHeader) -> Bool { if let other = other as? ChatMessageDateHeader, other.id == self.id { @@ -61,11 +62,11 @@ final class ChatMessageDateHeader: ListViewItemHeader { } } - func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { + public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { return ChatMessageDateHeaderNode(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action) } - func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { + public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { guard let node = node as? ChatMessageDateHeaderNode, let next = next as? ChatMessageDateHeader else { return } @@ -114,10 +115,10 @@ private func dateHeaderTimestampId(timestamp: Int32) -> Int32 { } } -final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { - let labelNode: TextNode - let backgroundNode: NavigationBackgroundNode - let stickBackgroundNode: ASImageNode +public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { + public let labelNode: TextNode + public let backgroundNode: NavigationBackgroundNode + public let stickBackgroundNode: ASImageNode private var backgroundContent: WallpaperBubbleBackgroundNode? @@ -133,7 +134,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { private var absolutePosition: (CGRect, CGSize)? - init(localTimestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { + public init(localTimestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { self.presentationData = presentationData self.controllerInteraction = controllerInteraction self.context = context @@ -219,13 +220,13 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size) } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(ListViewTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) { + public func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) { let previousPresentationData = self.presentationData self.presentationData = presentationData @@ -251,11 +252,11 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.setNeedsLayout() } - func updateBackgroundColor(color: UIColor, enableBlur: Bool) { + public func updateBackgroundColor(color: UIColor, enableBlur: Bool) { self.backgroundNode.updateColor(color: color, enableBlur: enableBlur, transition: .immediate) } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame @@ -265,7 +266,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { } } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { let chatDateSize: CGFloat = 20.0 let chatDateInset: CGFloat = 6.0 @@ -293,7 +294,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { } } - override func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { + override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { if !self.stickDistanceFactor.isEqual(to: factor) { self.stickBackgroundNode.alpha = factor @@ -311,7 +312,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { } } - override func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { + override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { self.flashingOnScrolling = isFlashingOnScrolling self.updateFlashing(animated: animated) } @@ -335,7 +336,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil } @@ -348,35 +349,35 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { return nil } - override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + override public func touchesCancelled(_ touches: Set?, with event: UIEvent?) { super.touchesCancelled(touches, with: event) } - @objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { + @objc private func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { if case .ended = recognizer.state { self.action?(self.localTimestamp, self.stickDistanceFactor < 0.5) } } } -final class ChatMessageAvatarHeader: ListViewItemHeader { - struct Id: Hashable { - var peerId: PeerId - var timestampId: Int32 +public final class ChatMessageAvatarHeader: ListViewItemHeader { + public struct Id: Hashable { + public var peerId: PeerId + public var timestampId: Int32 } - let id: ListViewItemNode.HeaderId - let peerId: PeerId - let peer: Peer? - let messageReference: MessageReference? - let adMessageId: EngineMessage.Id? - let effectiveTimestamp: Int32 - let presentationData: ChatPresentationData - let context: AccountContext - let controllerInteraction: ChatControllerInteraction - let storyStats: PeerStoryStats? + public let id: ListViewItemNode.HeaderId + public let peerId: PeerId + public let peer: Peer? + public let messageReference: MessageReference? + public let adMessageId: EngineMessage.Id? + public let effectiveTimestamp: Int32 + public let presentationData: ChatPresentationData + public let context: AccountContext + public let controllerInteraction: ChatControllerInteraction + public let storyStats: PeerStoryStats? - init(timestamp: Int32, peerId: PeerId, peer: Peer?, messageReference: MessageReference?, message: Message, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, storyStats: PeerStoryStats?) { + public init(timestamp: Int32, peerId: PeerId, peer: Peer?, messageReference: MessageReference?, message: Message, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, storyStats: PeerStoryStats?) { self.peerId = peerId self.peer = peer self.messageReference = messageReference @@ -399,10 +400,10 @@ final class ChatMessageAvatarHeader: ListViewItemHeader { self.storyStats = storyStats } - let stickDirection: ListViewItemHeaderStickDirection = .top - let stickOverInsets: Bool = false + public let stickDirection: ListViewItemHeaderStickDirection = .top + public let stickOverInsets: Bool = false - let height: CGFloat = 38.0 + public let height: CGFloat = 38.0 public func combinesWith(other: ListViewItemHeader) -> Bool { if let other = other as? ChatMessageAvatarHeader, other.id == self.id { @@ -415,12 +416,12 @@ final class ChatMessageAvatarHeader: ListViewItemHeader { } } - func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { - return ChatMessageAvatarHeaderNode(peerId: self.peerId, peer: self.peer, messageReference: self.messageReference, adMessageId: self.adMessageId, presentationData: self.presentationData, context: self.context, controllerInteraction: self.controllerInteraction, storyStats: self.storyStats, synchronousLoad: synchronousLoad) + public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { + return ChatMessageAvatarHeaderNodeImpl(peerId: self.peerId, peer: self.peer, messageReference: self.messageReference, adMessageId: self.adMessageId, presentationData: self.presentationData, context: self.context, controllerInteraction: self.controllerInteraction, storyStats: self.storyStats, synchronousLoad: synchronousLoad) } - func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { - guard let node = node as? ChatMessageAvatarHeaderNode else { + public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { + guard let node = node as? ChatMessageAvatarHeaderNodeImpl else { return } node.updatePresentationData(self.presentationData, context: self.context) @@ -432,7 +433,7 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0) private let maxVideoLoopCount = 3 -final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { +public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode { private let context: AccountContext private var presentationData: ChatPresentationData private let controllerInteraction: ChatControllerInteraction @@ -444,7 +445,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { private let adMessageId: EngineMessage.Id? private let containerNode: ContextControllerSourceNode - let avatarNode: AvatarNode + public let avatarNode: AvatarNode private var avatarVideoNode: AvatarVideoNode? private var cachedDataDisposable = MetaDisposable() @@ -465,7 +466,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { } } - init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, adMessageId: EngineMessage.Id?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, storyStats: PeerStoryStats?, synchronousLoad: Bool) { + public init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, adMessageId: EngineMessage.Id?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, storyStats: PeerStoryStats?, synchronousLoad: Bool) { self.peerId = peerId self.peer = peer self.messageReference = messageReference @@ -512,13 +513,13 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { self.cachedDataDisposable.dispose() } - func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor) { + public func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor) { self.containerNode.isGestureEnabled = false self.avatarNode.setCustomLetters(letters, icon: !letters.isEmpty ? nil : .phone) } - func setPeer(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, peer: Peer, authorOfMessage: MessageReference?, emptyColor: UIColor) { + public func setPeer(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, peer: Peer, authorOfMessage: MessageReference?, emptyColor: UIColor) { self.containerNode.isGestureEnabled = peer.smallProfileImage != nil var overrideImage: AvatarNodeImageOverride? @@ -607,7 +608,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { } } - func updateStoryStats(storyStats: PeerStoryStats?, theme: PresentationTheme, force: Bool) { + public func updateStoryStats(storyStats: PeerStoryStats?, theme: PresentationTheme, force: Bool) { /*if storyStats != nil { var backgroundContent: WallpaperBubbleBackgroundNode? if let current = self.backgroundContent { @@ -656,42 +657,42 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { }*/ } - override func didLoad() { + override public func didLoad() { super.didLoad() self.avatarNode.view.addGestureRecognizer(ListViewTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) { + public func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) { if self.presentationData !== presentationData { self.presentationData = presentationData self.setNeedsLayout() } } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { self.containerNode.frame = CGRect(origin: CGPoint(x: leftInset + 3.0, y: 0.0), size: CGSize(width: 38.0, height: 38.0)) self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0)) } - override func animateRemoved(duration: Double) { + override public func animateRemoved(duration: Double) { self.alpha = 0.0 self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) self.avatarNode.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false) } - override func animateAdded(duration: Double) { + override public func animateAdded(duration: Double) { self.layer.animateAlpha(from: 0.0, to: self.alpha, duration: 0.2) self.avatarNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2) } - override func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { + override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { } - override func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { + override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { } - func updateSelectionState(animated: Bool) { + public func updateSelectionState(animated: Bool) { let offset: CGFloat = self.controllerInteraction.selectionState != nil ? 42.0 : 0.0 let previousSubnodeTransform = self.subnodeTransform @@ -701,7 +702,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil } @@ -709,11 +710,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { return result } - override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + override public func touchesCancelled(_ touches: Set?, with event: UIEvent?) { super.touchesCancelled(touches, with: event) } - @objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { + @objc private func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { if case .ended = recognizer.state { if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _) = self.messageReference?.content { self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame) diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift similarity index 83% rename from submodules/TelegramUI/Sources/ChatMessageItem.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index e89c64e6d2..4ed32c279c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -12,74 +12,11 @@ import Emoji import PersistentStringHash import ChatControllerInteraction import ChatHistoryEntry - -public enum ChatMessageItemContent: Sequence { - case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: MessageHistoryEntryLocation?) - case group(messages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)]) - - func effectivelyIncoming(_ accountPeerId: PeerId, associatedData: ChatMessageItemAssociatedData? = nil) -> Bool { - if let subject = associatedData?.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { - return false - } - switch self { - case let .message(message, _, _, _, _): - return message.effectivelyIncoming(accountPeerId) - case let .group(messages): - return messages[0].0.effectivelyIncoming(accountPeerId) - } - } - - var index: MessageIndex { - switch self { - case let .message(message, _, _, _, _): - return message.index - case let .group(messages): - return messages[0].0.index - } - } - - var firstMessage: Message { - switch self { - case let .message(message, _, _, _, _): - return message - case let .group(messages): - return messages[0].0 - } - } - - var firstMessageAttributes: ChatMessageEntryAttributes { - switch self { - case let .message(_, _, _, attributes, _): - return attributes - case let .group(messages): - return messages[0].3 - } - } - - public func makeIterator() -> AnyIterator<(Message, ChatMessageEntryAttributes)> { - var index = 0 - return AnyIterator { () -> (Message, ChatMessageEntryAttributes)? in - switch self { - case let .message(message, _, _, attributes, _): - if index == 0 { - index += 1 - return (message, attributes) - } else { - index += 1 - return nil - } - case let .group(messages): - if index < messages.count { - let currentIndex = index - index += 1 - return (messages[currentIndex].0, messages[currentIndex].3) - } else { - return nil - } - } - } - } -} +import ChatMessageItem +import ChatMessageItemView +import ChatMessageStickerItemNode +import ChatMessageAnimatedStickerItemNode +import ChatMessageBubbleItemNode private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge { if let story = media as? TelegramMediaStory, story.isMention { @@ -200,10 +137,10 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs return .none } -func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) -> Bool{ +public func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) -> Bool{ let lhsHeader: ChatMessageDateHeader? let rhsHeader: ChatMessageDateHeader? - if let lhs = lhs as? ChatMessageItem { + if let lhs = lhs as? ChatMessageItemImpl { lhsHeader = lhs.dateHeader } else if let lhs = lhs as? ChatUnreadItem { lhsHeader = lhs.header @@ -213,7 +150,7 @@ func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) - lhsHeader = nil } if let rhs = rhs { - if let rhs = rhs as? ChatMessageItem { + if let rhs = rhs as? ChatMessageItemImpl { rhsHeader = rhs.dateHeader } else if let rhs = rhs as? ChatUnreadItem { rhsHeader = rhs.header @@ -232,43 +169,23 @@ func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) - } } -public enum ChatMessageItemAdditionalContent { - case eventLogPreviousMessage(Message) - case eventLogPreviousDescription(Message) - case eventLogPreviousLink(Message) -} - -enum ChatMessageMerge: Int32 { - case none = 0 - case fullyMerged = 1 - case semanticallyMerged = 2 - - var merged: Bool { - if case .none = self { - return false - } else { - return true - } - } -} - -public final class ChatMessageItem: ListViewItem, CustomStringConvertible { - let presentationData: ChatPresentationData - let context: AccountContext - let chatLocation: ChatLocation - let associatedData: ChatMessageItemAssociatedData - let controllerInteraction: ChatControllerInteraction - let content: ChatMessageItemContent - let disableDate: Bool - let effectiveAuthorId: PeerId? - let additionalContent: ChatMessageItemAdditionalContent? +public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible { + public let presentationData: ChatPresentationData + public let context: AccountContext + public let chatLocation: ChatLocation + public let associatedData: ChatMessageItemAssociatedData + public let controllerInteraction: ChatControllerInteraction + public let content: ChatMessageItemContent + public let disableDate: Bool + public let effectiveAuthorId: PeerId? + public let additionalContent: ChatMessageItemAdditionalContent? let dateHeader: ChatMessageDateHeader let avatarHeader: ChatMessageAvatarHeader? - let headers: [ListViewItemHeader] + public let headers: [ListViewItemHeader] - var message: Message { + public var message: Message { switch self.content { case let .message(message, _, _, _, _): return message @@ -277,7 +194,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } } - var read: Bool { + public var read: Bool { switch self.content { case let .message(_, read, _, _, _): return read @@ -286,7 +203,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } } - var unsent: Bool { + public var unsent: Bool { switch self.content { case let .message(message, _, _, _, _): return message.flags.contains(.Unsent) @@ -295,7 +212,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } } - var sending: Bool { + public var sending: Bool { switch self.content { case let .message(message, _, _, _, _): return message.flags.contains(.Sending) @@ -304,7 +221,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } } - var failed: Bool { + public var failed: Bool { switch self.content { case let .message(message, _, _, _, _): return message.flags.contains(.Failed) @@ -511,8 +428,13 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem) var disableDate = self.disableDate - if let subject = self.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind { - disableDate = true + if let subject = self.associatedData.subject, case let .messageOptions(_, _, info) = subject { + switch info { + case .reply, .link: + disableDate = true + default: + break + } } let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !disableDate) @@ -539,18 +461,18 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } } - final func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) { + public func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) { var mergedTop: ChatMessageMerge = .none var mergedBottom: ChatMessageMerge = .none var dateAtBottom = false - if let top = top as? ChatMessageItem { + if let top = top as? ChatMessageItemImpl { if top.dateHeader.id != self.dateHeader.id { mergedBottom = .none } else { mergedBottom = messagesShouldBeMerged(accountPeerId: self.context.account.peerId, message, top.message) } } - if let bottom = bottom as? ChatMessageItem { + if let bottom = bottom as? ChatMessageItemImpl { if bottom.dateHeader.id != self.dateHeader.id { mergedTop = .none dateAtBottom = true diff --git a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift similarity index 85% rename from submodules/TelegramUI/Sources/ChatReplyCountItem.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift index 855eb10ec8..e16d544a70 100644 --- a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift @@ -12,15 +12,15 @@ import ChatMessageItemCommon private let titleFont = UIFont.systemFont(ofSize: 13.0) -class ChatReplyCountItem: ListViewItem { - let index: MessageIndex - let isComments: Bool - let count: Int - let presentationData: ChatPresentationData - let header: ChatMessageDateHeader - let controllerInteraction: ChatControllerInteraction +public class ChatReplyCountItem: ListViewItem { + public let index: MessageIndex + public let isComments: Bool + public let count: Int + public let presentationData: ChatPresentationData + public let header: ChatMessageDateHeader + public let controllerInteraction: ChatControllerInteraction - init(index: MessageIndex, isComments: Bool, count: Int, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction) { + public init(index: MessageIndex, isComments: Bool, count: Int, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction) { self.index = index self.isComments = isComments self.count = count @@ -29,7 +29,7 @@ class ChatReplyCountItem: ListViewItem { self.controllerInteraction = controllerInteraction } - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { let node = ChatReplyCountItemNode() Queue.mainQueue().async { @@ -63,8 +63,8 @@ class ChatReplyCountItem: ListViewItem { } } -class ChatReplyCountItemNode: ListViewItemNode { - var item: ChatReplyCountItem? +public class ChatReplyCountItemNode: ListViewItemNode { + public var item: ChatReplyCountItem? private let labelNode: TextNode private var backgroundNode: WallpaperBubbleBackgroundNode? private let backgroundColorNode: ASDisplayNode @@ -75,7 +75,7 @@ class ChatReplyCountItemNode: ListViewItemNode { private var absoluteRect: (CGRect, CGSize)? - init() { + public init() { self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false @@ -91,17 +91,17 @@ class ChatReplyCountItemNode: ListViewItemNode { self.canBeUsedAsScrollToItemAnchor = false } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { if let item = item as? ChatReplyCountItem { let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem) let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom) @@ -111,7 +111,7 @@ class ChatReplyCountItemNode: ListViewItemNode { } } - func asyncLayout() -> (_ item: ChatReplyCountItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { + public func asyncLayout() -> (_ item: ChatReplyCountItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let layoutConstants = self.layoutConstants @@ -192,7 +192,7 @@ class ChatReplyCountItemNode: ListViewItemNode { } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { var rect = rect rect.origin.y = containerSize.height - rect.maxY + self.insets.top @@ -207,7 +207,7 @@ class ChatReplyCountItemNode: ListViewItemNode { } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let backgroundNode = self.backgroundNode { backgroundNode.offset(value: CGPoint(x: value.x, y: -value.y), animationCurve: animationCurve, duration: duration) } diff --git a/submodules/TelegramUI/Sources/ChatUnreadItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatUnreadItem.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift index 87cfa3ffa8..95604423a5 100644 --- a/submodules/TelegramUI/Sources/ChatUnreadItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift @@ -12,20 +12,20 @@ import ChatMessageItemCommon private let titleFont = UIFont.systemFont(ofSize: 13.0) -class ChatUnreadItem: ListViewItem { - let index: MessageIndex - let presentationData: ChatPresentationData - let controllerInteraction: ChatControllerInteraction - let header: ChatMessageDateHeader +public class ChatUnreadItem: ListViewItem { + public let index: MessageIndex + public let presentationData: ChatPresentationData + public let controllerInteraction: ChatControllerInteraction + public let header: ChatMessageDateHeader - init(index: MessageIndex, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, context: AccountContext) { + public init(index: MessageIndex, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, context: AccountContext) { self.index = index self.presentationData = presentationData self.controllerInteraction = controllerInteraction self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) } - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { let node = ChatUnreadItemNode() @@ -67,12 +67,12 @@ class ChatUnreadItem: ListViewItem { } } -class ChatUnreadItemNode: ListViewItemNode { - var item: ChatUnreadItem? - let backgroundNode: ASImageNode - let labelNode: TextNode +public class ChatUnreadItemNode: ListViewItemNode { + public var item: ChatUnreadItem? + public let backgroundNode: ASImageNode + public let labelNode: TextNode - let activateArea: AccessibilityAreaNode + public let activateArea: AccessibilityAreaNode private var wallpaperBackgroundNode: WallpaperBackgroundNode? private var backgroundContent: WallpaperBubbleBackgroundNode? @@ -83,7 +83,7 @@ class ChatUnreadItemNode: ListViewItemNode { private let layoutConstants = ChatMessageItemLayoutConstants.default - init() { + public init() { self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.displayWithoutProcessing = true @@ -108,17 +108,17 @@ class ChatUnreadItemNode: ListViewItemNode { self.canBeUsedAsScrollToItemAnchor = false } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { if let item = item as? ChatUnreadItem { let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem) let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom) @@ -128,7 +128,7 @@ class ChatUnreadItemNode: ListViewItemNode { } } - func asyncLayout() -> (_ item: ChatUnreadItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { + public func asyncLayout() -> (_ item: ChatUnreadItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { let labelLayout = TextNode.asyncLayout(self.labelNode) let layoutConstants = self.layoutConstants let currentTheme = self.theme @@ -187,7 +187,7 @@ class ChatUnreadItemNode: ListViewItemNode { } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { super.updateAbsoluteRect(rect, within: containerSize) self.absolutePosition = (rect, containerSize) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD new file mode 100644 index 0000000000..597406f49a --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD @@ -0,0 +1,31 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageItemView", + module_name = "ChatMessageItemView", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/ContextUI", + "//submodules/ChatListUI", + "//submodules/TelegramPresentationData", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift similarity index 91% rename from submodules/TelegramUI/Sources/ChatMessageItemView.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index e0d67f9a06..d16216c1df 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -12,8 +12,10 @@ import TelegramPresentationData import SwiftSignalKit import ChatControllerInteraction import ChatMessageItemCommon +import TextFormat +import ChatMessageItem -func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutConstants, ChatMessageItemLayoutConstants), params: ListViewItemLayoutParams, presentationData: ChatPresentationData) -> ChatMessageItemLayoutConstants { +public func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutConstants, ChatMessageItemLayoutConstants), params: ListViewItemLayoutParams, presentationData: ChatPresentationData) -> ChatMessageItemLayoutConstants { var result: ChatMessageItemLayoutConstants if params.width > 680.0 { result = constants.1 @@ -34,7 +36,7 @@ func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutConstants return result } -enum ChatMessageItemBottomNeighbor { +public enum ChatMessageItemBottomNeighbor { case none case merged(semi: Bool) } @@ -59,30 +61,30 @@ private let fileSizeFormatter: ByteCountFormatter = { return formatter }() -enum ChatMessageAccessibilityCustomActionType { +public enum ChatMessageAccessibilityCustomActionType { case reply case options } -final class ChatMessageAccessibilityCustomAction: UIAccessibilityCustomAction { - let action: ChatMessageAccessibilityCustomActionType +public final class ChatMessageAccessibilityCustomAction: UIAccessibilityCustomAction { + public let action: ChatMessageAccessibilityCustomActionType - init(name: String, target: Any?, selector: Selector, action: ChatMessageAccessibilityCustomActionType) { + public init(name: String, target: Any?, selector: Selector, action: ChatMessageAccessibilityCustomActionType) { self.action = action super.init(name: name, target: target, selector: selector) } } -final class ChatMessageAccessibilityData { - let label: String? - let value: String? - let hint: String? - let traits: UIAccessibilityTraits - let customActions: [ChatMessageAccessibilityCustomAction]? - let singleUrl: String? +public final class ChatMessageAccessibilityData { + public let label: String? + public let value: String? + public let hint: String? + public let traits: UIAccessibilityTraits + public let customActions: [ChatMessageAccessibilityCustomAction]? + public let singleUrl: String? - init(item: ChatMessageItem, isSelected: Bool?) { + public init(item: ChatMessageItem, isSelected: Bool?) { var hint: String? var traits: UIAccessibilityTraits = [] var singleUrl: String? @@ -612,14 +614,20 @@ final class ChatMessageAccessibilityData { } } -public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { - let layoutConstants = (ChatMessageItemLayoutConstants.compact, ChatMessageItemLayoutConstants.regular) +public enum InternalBubbleTapAction { + case action(() -> Void) + case optionalAction(() -> Void) + case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) +} + +open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { + public let layoutConstants = (ChatMessageItemLayoutConstants.compact, ChatMessageItemLayoutConstants.regular) - var item: ChatMessageItem? - var accessibilityData: ChatMessageAccessibilityData? - var safeInsets = UIEdgeInsets() + open var item: ChatMessageItem? + open var accessibilityData: ChatMessageAccessibilityData? + open var safeInsets = UIEdgeInsets() - var awaitingAppliedReaction: (MessageReaction.Reaction?, () -> Void)? + open var awaitingAppliedReaction: (MessageReaction.Reaction?, () -> Void)? public required convenience init() { self.init(layerBacked: false) @@ -634,22 +642,22 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol fatalError("init(coder:) has not been implemented") } - override public func reuse() { + override open func reuse() { super.reuse() self.item = nil self.frame = CGRect() } - func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { + open func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { self.item = item } - func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { + open func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { self.accessibilityData = accessibilityData } - override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + override open func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { if let item = item as? ChatMessageItem { let doLayout = self.asyncLayout() let merged = item.mergedWithItems(top: previousItem, bottom: nextItem) @@ -660,10 +668,10 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func cancelInsertionAnimations() { + open func cancelInsertionAnimations() { } - override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override open func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { if short { //self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height, to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) } else { @@ -672,7 +680,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { + open func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { return { _, _, _, _, _ in return (ListViewItemNodeLayout(contentSize: CGSize(width: 32.0, height: 32.0), insets: UIEdgeInsets()), { _, _, _ in @@ -680,24 +688,24 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + open func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { + open func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { return nil } - func updateHiddenMedia() { + open func updateHiddenMedia() { } - func updateSelectionState(animated: Bool) { + open func updateSelectionState(animated: Bool) { } - func updateSearchTextHighlightState() { + open func updateSearchTextHighlightState() { } - func updateHighlightedState(animated: Bool) { + open func updateHighlightedState(animated: Bool) { var isHighlightedInOverlay = false if let item = self.item, let contextHighlightedState = item.controllerInteraction.contextHighlightedState { switch item.content { @@ -717,17 +725,17 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol self.isHighlightedInOverlay = isHighlightedInOverlay } - func updateAutomaticMediaDownloadSettings() { + open func updateAutomaticMediaDownloadSettings() { } - func updateStickerSettings(forceStopAnimations: Bool) { + open func updateStickerSettings(forceStopAnimations: Bool) { } - func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { + open func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return nil } - override public func headers() -> [ListViewItemHeader]? { + override open func headers() -> [ListViewItemHeader]? { if let item = self.item { return item.headers } else { @@ -735,7 +743,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func performMessageButtonAction(button: ReplyMarkupButton) { + open func performMessageButtonAction(button: ReplyMarkupButton) { if let item = self.item { switch button.action { case .text: @@ -798,7 +806,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func presentMessageButtonContextMenu(button: ReplyMarkupButton) { + open func presentMessageButtonContextMenu(button: ReplyMarkupButton) { if let item = self.item { switch button.action { case let .url(url): @@ -809,24 +817,24 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func openMessageContextMenu() { + open func openMessageContextMenu() { } - public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { + open func targetReactionView(value: MessageReaction.Reaction) -> UIView? { return nil } - public func targetForStoryTransition(id: StoryId) -> UIView? { + open func targetForStoryTransition(id: StoryId) -> UIView? { return nil } - func getStatusNode() -> ASDisplayNode? { + open func getStatusNode() -> ASDisplayNode? { return nil } private var attachedAvatarNodeOffset: CGFloat = 0.0 - override public func attachedHeaderNodesUpdated() { + override open func attachedHeaderNodesUpdated() { if !self.attachedAvatarNodeOffset.isZero { self.updateAttachedAvatarNodeOffset(offset: self.attachedAvatarNodeOffset, transition: .immediate) } else { @@ -838,7 +846,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func updateAttachedAvatarNodeOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { + open func updateAttachedAvatarNodeOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { self.attachedAvatarNodeOffset = offset for headerNode in self.attachedHeaderNodes { if let headerNode = headerNode as? ChatMessageAvatarHeaderNode { @@ -847,10 +855,10 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol } } - func unreadMessageRangeUpdated() { + open func unreadMessageRangeUpdated() { } - public func contentFrame() -> CGRect { + open func contentFrame() -> CGRect { return self.bounds } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD new file mode 100644 index 0000000000..39944cc9aa --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageMapBubbleContentNode", + module_name = "ChatMessageMapBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/LiveLocationTimerNode", + "//submodules/PhotoResources", + "//submodules/MediaResources", + "//submodules/LocationResources", + "//submodules/LiveLocationPositionNode", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramStringFormatting", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageLiveLocationTextNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageLiveLocationTextNode.swift similarity index 100% rename from submodules/TelegramUI/Sources/ChatMessageLiveLocationTextNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageLiveLocationTextNode.swift diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift index 0551335876..aaa277a33c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift @@ -18,7 +18,7 @@ private let titleFont = Font.medium(14.0) private let liveTitleFont = Font.medium(16.0) private let textFont = Font.regular(14.0) -class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { private let imageNode: TransformImageNode private let pinNode: ChatMessageLiveLocationPositionNode private let dateAndStatusNode: ChatMessageDateAndStatusNode @@ -31,7 +31,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { private var timeoutTimer: (SwiftSignalKit.Timer, Int32)? - required init() { + required public init() { self.imageNode = TransformImageNode() self.imageNode.contentAnimations = [.subsequentUpdates] self.pinNode = ChatMessageLiveLocationPositionNode() @@ -45,14 +45,14 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.pinNode) } - override func accessibilityActivate() -> Bool { + override public func accessibilityActivate() -> Bool { if let item = self.item { let _ = item.controllerInteraction.openMessage(item.message, .default) } return true } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -60,14 +60,14 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { self.timeoutTimer?.0.invalidate() } - override func didLoad() { + override public func didLoad() { super.didLoad() let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.imageTap(_:))) self.view.addGestureRecognizer(tapRecognizer) } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeImageLayout = self.imageNode.asyncLayout() let makePinLayout = self.pinNode.asyncLayout() let statusLayout = self.dateAndStatusNode.asyncLayout() @@ -92,7 +92,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } var incoming = item.message.effectivelyIncoming(item.context.account.peerId) - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } @@ -467,19 +467,19 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(to: media) { let imageNode = self.imageNode return (self.imageNode, self.imageNode.bounds, { [weak imageNode] in @@ -489,7 +489,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { return nil } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { var mediaHidden = false if let currentMedia = self.media, let media = media { for item in media { @@ -504,11 +504,11 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { return mediaHidden } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { return .none } - @objc func imageTap(_ recognizer: UITapGestureRecognizer) { + @objc private func imageTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { if let item = self.item { let _ = item.controllerInteraction.openMessage(item.message, .default) @@ -516,7 +516,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.dateAndStatusNode.isHidden { return self.dateAndStatusNode.reactionView(value: value) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD new file mode 100644 index 0000000000..5de32fcb35 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD @@ -0,0 +1,31 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageMediaBubbleContentNode", + module_name = "ChatMessageMediaBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/GridMessageSelectionNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 71e31e9705..b692daefc8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -13,9 +13,10 @@ import ChatControllerInteraction import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageInteractiveMediaNode -class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { - override var supportsMosaic: Bool { +public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { + override public var supportsMosaic: Bool { return true } @@ -26,13 +27,13 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { private var media: Media? private var automaticPlayback: Bool? - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.interactiveImageNode.visibility = self.visibility != .none } } - required init() { + required public init() { self.interactiveImageNode = ChatMessageInteractiveMediaNode() super.init() @@ -71,11 +72,11 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let interactiveImageLayout = self.interactiveImageNode.asyncLayout() return { item, layoutConstants, preparePosition, selection, constrainedSize, _ in @@ -401,7 +402,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId, var currentMedia = self.media { if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia { currentMedia = fullMedia @@ -413,7 +414,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { return nil } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { var mediaHidden = false var currentMedia = self.media @@ -449,31 +450,31 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { return mediaHidden } - override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { + override public func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.interactiveImageNode.playMediaWithSound() } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { return .none } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func updateHighlightedState(animated: Bool) -> Bool { + override public func updateHighlightedState(animated: Bool) -> Bool { guard let item = self.item else { return false } @@ -492,7 +493,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { return false } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.interactiveImageNode.dateAndStatusNode.isHidden { return self.interactiveImageNode.dateAndStatusNode.reactionView(value: value) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD new file mode 100644 index 0000000000..175efd024c --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessagePollBubbleContentNode", + module_name = "ChatMessagePollBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/TextFormat", + "//submodules/UrlEscaping", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AccountContext", + "//submodules/AvatarNode", + "//submodules/TelegramPresentationData", + "//submodules/ChatMessageBackground", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index de0978ffdd..bc4b21fc69 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -14,103 +14,7 @@ import ChatMessageBackground import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon - -func isPollEffectivelyClosed(message: Message, poll: TelegramMediaPoll) -> Bool { - if poll.isClosed { - return true - }/* else if let deadlineTimeout = poll.deadlineTimeout, message.id.namespace == Namespaces.Message.Cloud { - let startDate: Int32 - if let forwardInfo = message.forwardInfo { - startDate = forwardInfo.date - } else { - startDate = message.timestamp - } - - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - if timestamp >= startDate + deadlineTimeout { - return true - } else { - return false - } - }*/ else { - return false - } -} - -private struct PercentCounterItem: Comparable { - var index: Int = 0 - var percent: Int = 0 - var remainder: Int = 0 - - static func <(lhs: PercentCounterItem, rhs: PercentCounterItem) -> Bool { - if lhs.remainder > rhs.remainder { - return true - } else if lhs.remainder < rhs.remainder { - return false - } - return lhs.percent < rhs.percent - } - -} - -private func adjustPercentCount(_ items: [PercentCounterItem], left: Int) -> [PercentCounterItem] { - var left = left - var items = items.sorted(by: <) - var i:Int = 0 - while i != items.count { - let item = items[i] - var j = i + 1 - loop: while j != items.count { - if items[j].percent != item.percent || items[j].remainder != item.remainder { - break loop - } - j += 1 - } - if items[i].remainder == 0 { - break - } - let equal = j - i - if equal <= left { - left -= equal - while i != j { - items[i].percent += 1 - i += 1 - } - } else { - i = j - } - } - return items -} - -func countNicePercent(votes: [Int], total: Int) -> [Int] { - var result:[Int] = [] - var items:[PercentCounterItem] = [] - for _ in votes { - result.append(0) - items.append(PercentCounterItem()) - } - - let count = votes.count - - var left:Int = 100 - for i in 0 ..< votes.count { - let votes = votes[i] - items[i].index = i - items[i].percent = Int((Float(votes) * 100) / Float(total)) - items[i].remainder = (votes * 100) - (items[i].percent * total) - left -= items[i].percent - } - - if left > 0 && left <= count { - items = adjustPercentCount(items, left: left) - } - for item in items { - result[item.index] = item.percent - } - - return result -} +import PollBubbleTimerNode private final class ChatMessagePollOptionRadioNodeParameters: NSObject { let timestamp: Double @@ -867,7 +771,7 @@ private final class SolutionButtonNode: HighlightableButtonNode { } } -class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { private let textNode: TextNode private let typeNode: TextNode private var timerNode: PollBubbleTimerNode? @@ -883,11 +787,11 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { private var poll: TelegramMediaPoll? - var solutionTipSourceNode: ASDisplayNode { + public var solutionTipSourceNode: ASDisplayNode { return self.solutionButtonNode } - required init() { + required public init() { self.textNode = TextNode() self.textNode.isUserInteractionEnabled = false self.textNode.contentMode = .topLeft @@ -978,7 +882,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -1008,7 +912,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTypeLayout = TextNode.asyncLayout(self.typeNode) let makeVotersLayout = TextNode.asyncLayout(self.votersNode) @@ -1681,22 +1585,22 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { self.avatarsNode.isUserInteractionEnabled = !self.buttonViewResultsTextNode.isHidden } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { @@ -1778,7 +1682,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } - func updatePollTooltipMessageState(animated: Bool) { + public func updatePollTooltipMessageState(animated: Bool) { guard let item = self.item else { return } @@ -1795,7 +1699,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.statusNode.isHidden { return self.statusNode.reactionView(value: value) } @@ -1849,7 +1753,7 @@ private let defaultBorderWidth: CGFloat = 1.0 private let avatarFont = avatarPlaceholderFont(size: 8.0) -final class MergedAvatarsNode: ASDisplayNode { +public final class MergedAvatarsNode: ASDisplayNode { private var peers: [PeerAvatarReference] = [] private var images: [PeerId: UIImage] = [:] private var disposables: [PeerId: Disposable] = [:] @@ -1858,9 +1762,9 @@ final class MergedAvatarsNode: ASDisplayNode { private var imageSpacing: CGFloat = defaultMergedImageSpacing private var borderWidthValue: CGFloat = defaultBorderWidth - var pressed: (() -> Void)? + public var pressed: (() -> Void)? - override init() { + override public init() { self.buttonNode = HighlightTrackingButtonNode() super.init() @@ -1881,11 +1785,11 @@ final class MergedAvatarsNode: ASDisplayNode { self.pressed?() } - func updateLayout(size: CGSize) { + public func updateLayout(size: CGSize) { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) } - func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { + public func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { self.imageSize = imageSize self.imageSpacing = imageSpacing self.borderWidthValue = borderWidth @@ -1949,11 +1853,11 @@ final class MergedAvatarsNode: ASDisplayNode { } } - override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { + override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue) } - @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { assertNotOnMainThread() let context = UIGraphicsGetCurrentContext()! diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD new file mode 100644 index 0000000000..cfe550c3ae --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageProfilePhotoSuggestionContentNode", + module_name = "ChatMessageProfilePhotoSuggestionContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/LocalizedPeerData", + "//submodules/TelegramStringFormatting", + "//submodules/WallpaperBackgroundNode", + "//submodules/ReactionSelectionNode", + "//submodules/PhotoResources", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramUniversalVideoContent", + "//submodules/GalleryUI", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index 8b39e78ad5..36d8ba5556 100644 --- a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -21,7 +21,7 @@ import Markdown import ChatMessageBubbleContentNode import ChatMessageItemCommon -class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode { +public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode { private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private let mediaBackgroundNode: NavigationBackgroundNode private let subtitleNode: TextNode @@ -38,7 +38,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode private let fetchDisposable = MetaDisposable() - required init() { + required public init() { self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) self.mediaBackgroundNode.clipsToBounds = true self.mediaBackgroundNode.cornerRadius = 24.0 @@ -86,7 +86,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -94,7 +94,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode self.fetchDisposable.dispose() } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { @@ -109,7 +109,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode } } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { var mediaHidden = false var currentMedia: Media? if let item = item { @@ -145,7 +145,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode let _ = item.controllerInteraction.openMessage(item.message, .default) } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeImageLayout = self.imageNode.asyncLayout() let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) @@ -301,7 +301,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if let mediaBackgroundContent = self.mediaBackgroundContent { @@ -312,7 +312,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode } } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.mediaBackgroundNode.frame.contains(point) { return .openMessage } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD new file mode 100644 index 0000000000..5b4f65efeb --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageReactionsFooterContentNode", + module_name = "ChatMessageReactionsFooterContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/RadialStatusNode", + "//submodules/AnimatedCountLabelNode", + "//submodules/AnimatedAvatarSetNode", + "//submodules/Components/ReactionButtonListComponent", + "//submodules/AccountContext", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift similarity index 87% rename from submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift index 94ee3c1bdd..b27dcf2704 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift @@ -16,14 +16,14 @@ import ChatControllerInteraction import ChatMessageBubbleContentNode import ChatMessageItemCommon -final class MessageReactionButtonsNode: ASDisplayNode { - enum DisplayType { +public final class MessageReactionButtonsNode: ASDisplayNode { + public enum DisplayType { case incoming case outgoing case freeform } - enum DisplayAlignment { + public enum DisplayAlignment { case left case right } @@ -33,10 +33,10 @@ final class MessageReactionButtonsNode: ASDisplayNode { private var backgroundMaskView: UIView? private var backgroundMaskButtons: [MessageReaction.Reaction: UIView] = [:] - var reactionSelected: ((MessageReaction.Reaction) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? + public var reactionSelected: ((MessageReaction.Reaction) -> Void)? + public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? - override init() { + override public init() { self.container = ReactionButtonsAsyncLayoutContainer() super.init() @@ -46,10 +46,10 @@ final class MessageReactionButtonsNode: ASDisplayNode { } - func update() { + public func update() { } - func prepareUpdate( + public func prepareUpdate( context: AccountContext, presentationData: ChatPresentationData, presentationContext: ChatPresentationContext, @@ -387,7 +387,7 @@ final class MessageReactionButtonsNode: ASDisplayNode { private var absoluteRect: (CGRect, CGSize)? - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { self.absoluteRect = (rect, containerSize) if let bubbleBackgroundNode = self.bubbleBackgroundNode { @@ -395,7 +395,7 @@ final class MessageReactionButtonsNode: ASDisplayNode { } } - func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { self.absoluteRect = (rect, containerSize) if let bubbleBackgroundNode = self.bubbleBackgroundNode { @@ -403,19 +403,19 @@ final class MessageReactionButtonsNode: ASDisplayNode { } } - func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let bubbleBackgroundNode = self.bubbleBackgroundNode { bubbleBackgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } } - func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { if let bubbleBackgroundNode = self.bubbleBackgroundNode { bubbleBackgroundNode.offsetSpring(value: value, duration: duration, damping: damping) } } - func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { for (key, button) in self.container.buttons { if key == value { return button.view.iconView @@ -424,19 +424,19 @@ final class MessageReactionButtonsNode: ASDisplayNode { return nil } - func animateIn(animation: ListViewItemUpdateAnimation) { + public func animateIn(animation: ListViewItemUpdateAnimation) { for (_, button) in self.container.buttons { animation.animator.animateScale(layer: button.view.layer, from: 0.01, to: 1.0, completion: nil) } } - func animateOut(animation: ListViewItemUpdateAnimation) { + public func animateOut(animation: ListViewItemUpdateAnimation) { for (_, button) in self.container.buttons { animation.animator.updateScale(layer: button.view.layer, scale: 0.01, completion: nil) } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (_, button) in self.container.buttons { if button.view.frame.contains(point) { if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) { @@ -449,10 +449,10 @@ final class MessageReactionButtonsNode: ASDisplayNode { } } -final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode { private let buttonsNode: MessageReactionButtonsNode - required init() { + required public init() { self.buttonsNode = MessageReactionButtonsNode() super.init() @@ -476,11 +476,11 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let buttonsNode = self.buttonsNode return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -529,25 +529,25 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) self.buttonsNode.animateOut(animation: ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .spring, interactive: false))) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.bounds.height / 2.0), to: CGPoint(), duration: duration, removeOnCompletion: true, additive: true) } - override func animateRemovalFromBubble(_ duration: Double, completion: @escaping () -> Void) { + override public func animateRemovalFromBubble(_ duration: Double, completion: @escaping () -> Void) { self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.bounds.height / 2.0), duration: duration, removeOnCompletion: false, additive: true) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in completion() @@ -555,38 +555,38 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode self.buttonsNode.animateOut(animation: ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .spring, interactive: false))) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: nil), result !== self.buttonsNode.view { return .ignore } return .none } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event), result !== self.buttonsNode.view { return result } return nil } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { return self.buttonsNode.reactionTargetView(value: value) } } -final class ChatMessageReactionButtonsNode: ASDisplayNode { - final class Arguments { - let context: AccountContext - let presentationData: ChatPresentationData - let presentationContext: ChatPresentationContext - let availableReactions: AvailableReactions? - let reactions: ReactionsMessageAttribute - let message: Message - let accountPeer: EnginePeer? - let isIncoming: Bool - let constrainedWidth: CGFloat +public final class ChatMessageReactionButtonsNode: ASDisplayNode { + public final class Arguments { + public let context: AccountContext + public let presentationData: ChatPresentationData + public let presentationContext: ChatPresentationContext + public let availableReactions: AvailableReactions? + public let reactions: ReactionsMessageAttribute + public let message: Message + public let accountPeer: EnginePeer? + public let isIncoming: Bool + public let constrainedWidth: CGFloat - init( + public init( context: AccountContext, presentationData: ChatPresentationData, presentationContext: ChatPresentationContext, @@ -611,10 +611,10 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { private let buttonsNode: MessageReactionButtonsNode - var reactionSelected: ((MessageReaction.Reaction) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? + public var reactionSelected: ((MessageReaction.Reaction) -> Void)? + public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? - override init() { + override public init() { self.buttonsNode = MessageReactionButtonsNode() super.init() @@ -630,7 +630,7 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageReactionButtonsNode?) -> (_ arguments: ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageReactionButtonsNode?) -> (_ arguments: ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)) { return { arguments in let node = maybeNode ?? ChatMessageReactionButtonsNode() @@ -660,12 +660,12 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { } } - func animateIn(animation: ListViewItemUpdateAnimation) { + public func animateIn(animation: ListViewItemUpdateAnimation) { self.buttonsNode.animateIn(animation: animation) self.buttonsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - func animateOut(animation: ListViewItemUpdateAnimation, completion: @escaping () -> Void) { + public func animateOut(animation: ListViewItemUpdateAnimation, completion: @escaping () -> Void) { self.buttonsNode.animateOut(animation: animation) animation.animator.updateAlpha(layer: self.buttonsNode.layer, alpha: 0.0, completion: { _ in completion() @@ -673,30 +673,30 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { animation.animator.updateFrame(layer: self.buttonsNode.layer, frame: self.buttonsNode.layer.frame.offsetBy(dx: 0.0, dy: -self.buttonsNode.layer.bounds.height / 2.0), completion: nil) } - func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { return self.buttonsNode.reactionTargetView(value: value) } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event), result !== self.buttonsNode.view { return result } return nil } - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { self.buttonsNode.update(rect: rect, within: containerSize, transition: transition) } - func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { self.buttonsNode.update(rect: rect, within: containerSize, transition: transition) } - func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { self.buttonsNode.offset(value: value, animationCurve: animationCurve, duration: duration) } - func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { self.buttonsNode.offsetSpring(value: value, duration: duration, damping: damping) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD new file mode 100644 index 0000000000..a8bc3b4d00 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageRestrictedBubbleContentNode", + module_name = "ChatMessageRestrictedBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) + diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift similarity index 93% rename from submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift index 31ea6b63a4..c11a20eba4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -11,11 +11,11 @@ import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatMessageItemCommon -class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { private let textNode: TextNode private let statusNode: ChatMessageDateAndStatusNode - required init() { + required public init() { self.textNode = TextNode() self.statusNode = ChatMessageDateAndStatusNode() @@ -28,11 +28,11 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.textNode) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let textLayout = TextNode.asyncLayout(self.textNode) let statusLayout = self.statusNode.asyncLayout() @@ -207,17 +207,17 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD new file mode 100644 index 0000000000..3e8e689e0d --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageSelectionNode", + module_name = "ChatMessageSelectionNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/CheckNode", + "//submodules/TelegramCore", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageSelectionNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/Sources/ChatMessageSelectionNode.swift similarity index 74% rename from submodules/TelegramUI/Sources/ChatMessageSelectionNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/Sources/ChatMessageSelectionNode.swift index 3921dad579..2f9d7a0b10 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSelectionNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/Sources/ChatMessageSelectionNode.swift @@ -5,13 +5,13 @@ import TelegramPresentationData import CheckNode import TelegramCore -final class ChatMessageSelectionNode: ASDisplayNode { +public final class ChatMessageSelectionNode: ASDisplayNode { private let toggle: (Bool) -> Void - private(set) var selected = false + public private(set) var selected = false private let checkNode: CheckNode - init(wallpaper: TelegramWallpaper, theme: PresentationTheme, toggle: @escaping (Bool) -> Void) { + public init(wallpaper: TelegramWallpaper, theme: PresentationTheme, toggle: @escaping (Bool) -> Void) { self.toggle = toggle let style: CheckNodeTheme.Style @@ -29,26 +29,26 @@ final class ChatMessageSelectionNode: ASDisplayNode { self.addSubnode(self.checkNode) } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updateSelected(_ selected: Bool, animated: Bool) { + public func updateSelected(_ selected: Bool, animated: Bool) { if self.selected != selected { self.selected = selected self.checkNode.setSelected(selected, animated: animated) } } - @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.toggle(!self.selected) } } - func updateLayout(size: CGSize, leftInset: CGFloat) { + public func updateLayout(size: CGSize, leftInset: CGFloat) { let checkSize = CGSize(width: 28.0, height: 28.0) self.checkNode.frame = CGRect(origin: CGPoint(x: 6.0 + leftInset, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD new file mode 100644 index 0000000000..cc323f2e04 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageShareButton", + module_name = "ChatMessageShareButton", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift new file mode 100644 index 0000000000..f1f33becaa --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -0,0 +1,176 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import ChatControllerInteraction +import AccountContext +import TelegramCore +import Postbox +import WallpaperBackgroundNode +import ChatMessageItemCommon + +public class ChatMessageShareButton: HighlightableButtonNode { + private var backgroundContent: WallpaperBubbleBackgroundNode? + private var backgroundBlurView: PortalView? + + private let iconNode: ASImageNode + private var iconOffset = CGPoint() + + private var theme: PresentationTheme? + private var isReplies: Bool = false + + private var textNode: ImmediateTextNode? + + private var absolutePosition: (CGRect, CGSize)? + + public init() { + self.iconNode = ASImageNode() + + super.init(pointerStyle: nil) + + self.allowsGroupOpacity = true + + self.addSubnode(self.iconNode) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { + var isReplies = false + var replyCount = 0 + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + for attribute in message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + replyCount = Int(attribute.count) + isReplies = true + break + } + } + } + if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id { + replyCount = 0 + isReplies = false + } + if disableComments { + replyCount = 0 + isReplies = false + } + + if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { + self.theme = presentationData.theme.theme + self.isReplies = isReplies + + var updatedIconImage: UIImage? + var updatedIconOffset = CGPoint() + if case .pinnedMessages = subject { + updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) + } else if isReplies { + updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { + updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) + } else { + updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } + //self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate) + self.iconNode.image = updatedIconImage + self.iconOffset = updatedIconOffset + } + var size = CGSize(width: 30.0, height: 30.0) + var offsetIcon = false + if isReplies, replyCount > 0 { + offsetIcon = true + + let textNode: ImmediateTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ImmediateTextNode() + self.textNode = textNode + self.addSubnode(textNode) + } + + let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) + + let countString: String + if replyCount >= 1000 * 1000 { + countString = "\(replyCount / 1000_000)M" + } else if replyCount >= 1000 { + countString = "\(replyCount / 1000)K" + } else { + countString = "\(replyCount)" + } + + textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) + let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + size.height += textSize.height - 1.0 + textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) + } else if let textNode = self.textNode { + self.textNode = nil + textNode.removeFromSupernode() + } + + if self.backgroundBlurView == nil { + if let backgroundBlurView = controllerInteraction.presentationContext.backgroundNode?.makeFreeBackground() { + self.backgroundBlurView = backgroundBlurView + self.view.insertSubview(backgroundBlurView.view, at: 0) + + backgroundBlurView.view.clipsToBounds = true + } + } + if let backgroundBlurView = self.backgroundBlurView { + backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size) + backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0 + } + + //self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) + //self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0, transition: .immediate) + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.iconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.iconOffset.y), size: image.size) + } + + + if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { + if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + self.backgroundContent = backgroundContent + self.insertSubnode(backgroundContent, at: 0) + } + } else { + self.backgroundContent?.removeFromSupernode() + self.backgroundContent = nil + } + + if let backgroundContent = self.backgroundContent { + //self.backgroundNode.isHidden = true + self.backgroundBlurView?.view.isHidden = true + backgroundContent.cornerRadius = min(size.width, size.height) / 2.0 + backgroundContent.frame = CGRect(origin: CGPoint(), size: size) + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } else { + //self.backgroundNode.isHidden = false + self.backgroundBlurView?.view.isHidden = false + } + + return size + } + + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD new file mode 100644 index 0000000000..54233cea21 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageStickerItemNode", + module_name = "ChatMessageStickerItemNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/StickerResources", + "//submodules/ContextUI", + "//submodules/Markdown", + "//submodules/ShimmerEffect", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 737708c789..353a727f9d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -18,18 +18,28 @@ import ChatMessageForwardInfoNode import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageReplyInfoNode +import ChatMessageItem +import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageThreadInfoNode +import ChatMessageActionButtonsNode +import ChatMessageReactionsFooterContentNode +import ChatSwipeToReplyRecognizer private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont -class ChatMessageStickerItemNode: ChatMessageItemView { - let contextSourceNode: ContextExtractedContentContainingNode +public class ChatMessageStickerItemNode: ChatMessageItemView { + public let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode - let imageNode: TransformImageNode + public let imageNode: TransformImageNode private var backgroundNode: WallpaperBubbleBackgroundNode? private var placeholderNode: StickerShimmerEffectNode - var textNode: TextNode? + public var textNode: TextNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? @@ -38,7 +48,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var deliveryFailedNode: ChatMessageDeliveryFailedNode? private var shareButtonNode: ChatMessageShareButton? - var telegramFile: TelegramMediaFile? + public var telegramFile: TelegramMediaFile? private let fetchDisposable = MetaDisposable() private var viaBotNode: TextNode? @@ -65,7 +75,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var enableSynchronousImageApply: Bool = false - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none let isVisible = self.visibility != .none @@ -85,7 +95,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - required init() { + required public init() { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() self.imageNode = TransformImageNode() @@ -169,7 +179,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -177,7 +187,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.fetchDisposable.dispose() } - private func removePlaceholder(animated: Bool) { if !animated { self.placeholderNode.removeFromSupernode() @@ -189,7 +198,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -266,7 +275,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.view.addGestureRecognizer(replyRecognizer) } - override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { + override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { super.setupItem(item, synchronousLoad: synchronousLoad) self.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply @@ -289,7 +298,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } private var absoluteRect: (CGRect, CGSize)? - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if !self.contextSourceNode.isExtractedToContextPreview { var rect = rect @@ -343,7 +352,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let backgroundNode = self.backgroundNode { backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } @@ -353,7 +362,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { + override public func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { super.updateAccessibilityData(accessibilityData) self.messageAccessibilityArea.accessibilityLabel = accessibilityData.label @@ -384,7 +393,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { + override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { let displaySize = CGSize(width: 184.0, height: 184.0) let telegramFile = self.telegramFile let layoutConstants = self.layoutConstants @@ -1259,7 +1268,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { @@ -1381,7 +1390,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return nil } - @objc func shareButtonPressed() { + @objc private func shareButtonPressed() { if let item = self.item { if case .pinnedMessages = item.associatedData.subject { item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id) @@ -1413,7 +1422,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } private var playedSwipeToReplyHaptic = false - @objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { + @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 var leftOffset: CGFloat = 0.0 var swipeOffset: CGFloat = 45.0 @@ -1535,7 +1544,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view } @@ -1550,7 +1559,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return super.hitTest(point, with: event) } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { guard let item = self.item else { return } @@ -1658,7 +1667,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func updateHighlightedState(animated: Bool) { + override public func updateHighlightedState(animated: Bool) { super.updateHighlightedState(animated: animated) if let item = self.item { @@ -1681,37 +1690,51 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func cancelInsertionAnimations() { + override public func cancelInsertionAnimations() { self.layer.removeAllAnimations() } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { super.animateRemoved(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { super.animateAdded(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { + override public func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { return self.contextSourceNode } - override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + override public func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + public final class AnimationTransitionTextInput { + public let backgroundView: UIView + public let contentView: UIView + public let sourceRect: CGRect + public let scrollOffset: CGFloat - func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) { + public init(backgroundView: UIView, contentView: UIView, sourceRect: CGRect, scrollOffset: CGFloat) { + self.backgroundView = backgroundView + self.contentView = contentView + self.sourceRect = sourceRect + self.scrollOffset = scrollOffset + } + } + + public func animateContentFromTextInputField(textInput: AnimationTransitionTextInput, transition: CombinedTransition) { guard let _ = self.item else { return } @@ -1763,8 +1786,56 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) } + + public final class AnimationTransitionSticker { + public let imageNode: TransformImageNode? + public let animationNode: ASDisplayNode? + public let placeholderNode: ASDisplayNode? + public let imageLayer: CALayer? + public let relativeSourceRect: CGRect + + var sourceFrame: CGRect { + if let imageNode = self.imageNode { + return imageNode.frame + } else if let imageLayer = self.imageLayer { + return imageLayer.bounds + } else { + return CGRect(origin: CGPoint(), size: relativeSourceRect.size) + } + } + + var sourceLayer: CALayer? { + if let imageNode = self.imageNode { + return imageNode.layer + } else if let imageLayer = self.imageLayer { + return imageLayer + } else { + return nil + } + } + + func snapshotContentTree() -> UIView? { + if let animationNode = self.animationNode { + return animationNode.view.snapshotContentTree() + } else if let imageNode = self.imageNode { + return imageNode.view.snapshotContentTree() + } else if let sourceLayer = self.imageLayer { + return sourceLayer.snapshotContentTreeAsView() + } else { + return nil + } + } + + public init(imageNode: TransformImageNode?, animationNode: ASDisplayNode?, placeholderNode: ASDisplayNode?, imageLayer: CALayer?, relativeSourceRect: CGRect) { + self.imageNode = imageNode + self.animationNode = animationNode + self.placeholderNode = placeholderNode + self.imageLayer = imageLayer + self.relativeSourceRect = relativeSourceRect + } + } - func animateContentFromStickerGridItem(stickerSource: ChatMessageTransitionNode.Sticker, transition: CombinedTransition) { + public func animateContentFromStickerGridItem(stickerSource: AnimationTransitionSticker, transition: CombinedTransition) { guard let _ = self.item else { return } @@ -1848,8 +1919,26 @@ class ChatMessageStickerItemNode: ChatMessageItemView { placeholderNode.layer.animateAlpha(from: 0.0, to: placeholderNode.alpha, duration: 0.4) } } + + public final class AnimationTransitionReplyPanel { + public let titleNode: ASDisplayNode + public let textNode: ASDisplayNode + public let lineNode: ASDisplayNode + public let imageNode: ASDisplayNode + public let relativeSourceRect: CGRect + public let relativeTargetRect: CGRect - func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) { + public init(titleNode: ASDisplayNode, textNode: ASDisplayNode, lineNode: ASDisplayNode, imageNode: ASDisplayNode, relativeSourceRect: CGRect, relativeTargetRect: CGRect) { + self.titleNode = titleNode + self.textNode = textNode + self.lineNode = lineNode + self.imageNode = imageNode + self.relativeSourceRect = relativeSourceRect + self.relativeTargetRect = relativeTargetRect + } + } + + public func animateReplyPanel(sourceReplyPanel: AnimationTransitionReplyPanel, transition: CombinedTransition) { if let replyInfoNode = self.replyInfoNode { let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) let mappedPanel = ChatMessageReplyInfoNode.TransitionReplyPanel( @@ -1869,7 +1958,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) { + public func animateFromLoadingPlaceholder(delay: Double, transition: ContainedViewLayoutTransition) { guard let item = self.item else { return } @@ -1879,14 +1968,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView { transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay) } - override func openMessageContextMenu() { + override public func openMessageContextMenu() { guard let item = self.item else { return } item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } - override func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: StoryId) -> UIView? { guard let item = self.item else { return nil } @@ -1902,7 +1991,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return nil } - override func targetReactionView(value: MessageReaction.Reaction) -> UIView? { + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result } @@ -1912,7 +2001,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return nil } - override func contentFrame() -> CGRect { + override public func contentFrame() -> CGRect { return self.imageNode.frame } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD new file mode 100644 index 0000000000..a9aa212221 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD @@ -0,0 +1,40 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageStoryMentionContentNode", + module_name = "ChatMessageStoryMentionContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/LocalizedPeerData", + "//submodules/TelegramStringFormatting", + "//submodules/WallpaperBackgroundNode", + "//submodules/ReactionSelectionNode", + "//submodules/PhotoResources", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramUniversalVideoContent", + "//submodules/GalleryUI", + "//submodules/Markdown", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", + "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift index c04b8c0086..17ff7c398d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift @@ -24,7 +24,7 @@ import AvatarNode import ChatMessageBubbleContentNode import ChatMessageItemCommon -class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { +public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private let mediaBackgroundNode: NavigationBackgroundNode private let subtitleNode: TextNode @@ -38,7 +38,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { private let fetchDisposable = MetaDisposable() - required init() { + required public init() { self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) self.mediaBackgroundNode.clipsToBounds = true self.mediaBackgroundNode.cornerRadius = 24.0 @@ -86,7 +86,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -94,7 +94,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { self.fetchDisposable.dispose() } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { @@ -109,7 +109,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { } } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { var mediaHidden = false var currentMedia: Media? if let item = item { @@ -139,7 +139,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { let _ = item.controllerInteraction.openMessage(item.message, .default) } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeImageLayout = self.imageNode.asyncLayout() let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) @@ -348,7 +348,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if let mediaBackgroundContent = self.mediaBackgroundContent { @@ -359,7 +359,7 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { } } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.mediaBackgroundNode.frame.contains(point) { return .openMessage } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD new file mode 100644 index 0000000000..150eeba1ad --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageSwipeToReplyNode", + module_name = "ChatMessageSwipeToReplyNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/AppBundle", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/Sources/ChatMessageSwipeToReplyNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/Sources/ChatMessageSwipeToReplyNode.swift index 97aac1c46e..8a26af4a4a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/Sources/ChatMessageSwipeToReplyNode.swift @@ -8,8 +8,8 @@ import ChatControllerInteraction private let size = CGSize(width: 33.0, height: 33.0) -final class ChatMessageSwipeToReplyNode: ASDisplayNode { - enum Action { +public final class ChatMessageSwipeToReplyNode: ASDisplayNode { + public enum Action { case reply case like case unlike @@ -27,7 +27,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { private var absolutePosition: (CGRect, CGSize)? - init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) { + public init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) { self.backgroundNode = NavigationBackgroundNode(color: fillColor, enableBlur: enableBlur) self.backgroundNode.isUserInteractionEnabled = false @@ -138,7 +138,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } } - override func didLoad() { + override public func didLoad() { super.didLoad() if let backgroundContent = self.backgroundContent { @@ -149,7 +149,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } private var animatedWave = false - func updateProgress(_ progress: CGFloat) { + public func updateProgress(_ progress: CGFloat) { let progress = max(0.0, min(1.0, progress)) var foregroundProgress = min(1.0, progress * 1.2) var scaleProgress = 0.65 + foregroundProgress * 0.35 @@ -175,7 +175,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } } - func playSuccessAnimation() { + public func playSuccessAnimation() { guard !self.animatedWave else { return } @@ -214,7 +214,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { self.fillLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3) } - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame @@ -225,7 +225,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } } -extension ChatMessageSwipeToReplyNode.Action { +public extension ChatMessageSwipeToReplyNode.Action { init(_ action: ChatControllerInteractionSwipeAction?) { if let action = action { switch action { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 973930b7a3..f69a677894 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -67,6 +67,11 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private var cachedChatMessageText: CachedChatMessageText? + private var textSelectionState: Promise? + + private var linkPreviewOptionsDisposable: Disposable? + private var linkPreviewHighlightingNodes: [LinkHighlightingNode] = [] + override public var visibility: ListViewItemNodeVisibility { didSet { if oldValue != self.visibility { @@ -126,6 +131,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { fatalError("init(coder:) has not been implemented") } + deinit { + self.linkPreviewOptionsDisposable?.dispose() + } + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let textLayout = TextNodeWithEntities.asyncLayout(self.textNode) let spoilerTextLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode) @@ -140,7 +149,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let message = item.message let incoming: Bool - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .forward = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { incoming = false } else { incoming = item.message.effectivelyIncoming(item.context.account.peerId) @@ -564,15 +573,40 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.statusNode.pressed = nil } - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case let .reply(initialQuote) = info.kind { - if strongSelf.textSelectionNode == nil { - strongSelf.updateIsExtractedToContextPreview(true) - if let initialQuote, item.message.id == initialQuote.messageId, let string = strongSelf.textNode.textNode.cachedLayout?.attributedString { - let nsString = string.string as NSString - let subRange = nsString.range(of: initialQuote.text) - if subRange.location != NSNotFound { - strongSelf.beginTextSelection(range: subRange, displayMenu: false) + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject { + if case let .reply(info) = info { + if strongSelf.textSelectionNode == nil { + strongSelf.updateIsExtractedToContextPreview(true) + if let initialQuote = info.quote, item.message.id == initialQuote.messageId, let string = strongSelf.textNode.textNode.cachedLayout?.attributedString { + let nsString = string.string as NSString + let subRange = nsString.range(of: initialQuote.text) + if subRange.location != NSNotFound { + strongSelf.beginTextSelection(range: subRange, displayMenu: false) + } } + + if strongSelf.textSelectionState == nil { + if let textSelectionNode = strongSelf.textSelectionNode { + let range = textSelectionNode.getSelection() + strongSelf.textSelectionState = Promise(strongSelf.getSelectionState(range: range)) + } else { + strongSelf.textSelectionState = Promise(strongSelf.getSelectionState(range: nil)) + } + } + if let textSelectionState = strongSelf.textSelectionState { + info.selectionState.set(textSelectionState.get()) + } + } + } else if case let .link(link) = info { + if strongSelf.linkPreviewOptionsDisposable == nil { + strongSelf.linkPreviewOptionsDisposable = (link.options + |> deliverOnMainQueue).startStrict(next: { [weak strongSelf] options in + guard let strongSelf else { + return + } + + strongSelf.updateLinkPreviewTextHighlightState(text: options.url) + }) } } } @@ -599,6 +633,13 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if case .tap = gesture { + } else { + if let item = self.item, let subject = item.associatedData.subject, case .messageOptions = subject { + return .none + } + } + let textNodeFrame = self.textNode.textNode.frame if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) { @@ -750,7 +791,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { for i in 0 ..< rectsSet.count { let rects = rectsSet[i] let textHighlightNode: LinkHighlightingNode - if self.textHighlightingNodes.count < i { + if i < self.textHighlightingNodes.count { textHighlightNode = self.textHighlightingNodes[i] } else { textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.textHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.textHighlightColor) @@ -766,6 +807,39 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } + private func updateLinkPreviewTextHighlightState(text: String?) { + guard let item = self.item else { + return + } + var rectsSet: [[CGRect]] = [] + if let text = text, !text.isEmpty, let cachedLayout = self.textNode.textNode.cachedLayout, let string = cachedLayout.attributedString?.string { + let nsString = string as NSString + let range = nsString.range(of: text) + if range.location != NSNotFound { + if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty { + rectsSet = [rects] + } + } + } + for i in 0 ..< rectsSet.count { + let rects = rectsSet[i] + let textHighlightNode: LinkHighlightingNode + if i < self.linkPreviewHighlightingNodes.count { + textHighlightNode = self.linkPreviewHighlightingNodes[i] + } else { + textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor) + self.linkPreviewHighlightingNodes.append(textHighlightNode) + self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode) + } + textHighlightNode.frame = self.textNode.textNode.frame + textHighlightNode.updateRects(rects) + } + for i in (rectsSet.count ..< self.linkPreviewHighlightingNodes.count).reversed() { + self.linkPreviewHighlightingNodes[i].removeFromSupernode() + self.linkPreviewHighlightingNodes.remove(at: i) + } + } + override public func willUpdateIsExtractedToContextPreview(_ value: Bool) { if !value { if let textSelectionNode = self.textSelectionNode { @@ -799,11 +873,11 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { return } - /*if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind { + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .reply = info { item.controllerInteraction.presentControllerInCurrent(c, a) - } else {*/ + } else { item.controllerInteraction.presentGlobalOverlayController(c, a) - //} + } }, rootNode: { [weak rootNode] in return rootNode }, performAction: { [weak self] text, action in @@ -813,7 +887,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { item.controllerInteraction.performTextSelectionAction(item.message, true, text, action) }) textSelectionNode.updateRange = { [weak self] selectionRange in - if let strongSelf = self, let dustNode = strongSelf.dustNode, !dustNode.isRevealed, let textLayout = strongSelf.textNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty, let selectionRange = selectionRange { + guard let strongSelf = self else { + return + } + if let dustNode = strongSelf.dustNode, !dustNode.isRevealed, let textLayout = strongSelf.textNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty, let selectionRange = selectionRange { for (spoilerRange, _) in textLayout.spoilers { if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 { dustNode.update(revealed: true) @@ -821,23 +898,25 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } } + if let textSelectionState = strongSelf.textSelectionState { + textSelectionState.set(.single(strongSelf.getSelectionState(range: selectionRange))) + } } let enableCopy = !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected() textSelectionNode.enableCopy = enableCopy - var enableQuote = false + let enableQuote = true var enableOtherActions = true - if let subject = item.associatedData.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind { - enableQuote = true + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .reply = info { enableOtherActions = false } else if item.controllerInteraction.canSetupReply(item.message) == .reply { - enableQuote = true enableOtherActions = false } textSelectionNode.enableQuote = enableQuote textSelectionNode.enableTranslate = enableOtherActions textSelectionNode.enableShare = enableOtherActions + textSelectionNode.menuSkipCoordnateConversion = !enableOtherActions self.textSelectionNode = textSelectionNode self.addSubnode(textSelectionNode) self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode.textNode) @@ -904,11 +983,26 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { textSelectionNode.setSelection(range: range, displayMenu: displayMenu) } - public func getCurrentTextSelection() -> (text: String, entities: [MessageTextEntity])? { + public func cancelTextSelection() { + guard let textSelectionNode = self.textSelectionNode else { + return + } + textSelectionNode.cancelSelection() + } + + private func getSelectionState(range: NSRange?) -> ChatControllerSubject.MessageOptionsInfo.SelectionState { + var quote: ChatControllerSubject.MessageOptionsInfo.Quote? + if let item = self.item, let range, let selection = self.getCurrentTextSelection(customRange: range) { + quote = ChatControllerSubject.MessageOptionsInfo.Quote(messageId: item.message.id, text: selection.text) + } + return ChatControllerSubject.MessageOptionsInfo.SelectionState(quote: quote) + } + + public func getCurrentTextSelection(customRange: NSRange? = nil) -> (text: String, entities: [MessageTextEntity])? { guard let textSelectionNode = self.textSelectionNode else { return nil } - guard let range = textSelectionNode.getSelection() else { + guard let range = customRange ?? textSelectionNode.getSelection() else { return nil } guard let item = self.item else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD new file mode 100644 index 0000000000..a870c33413 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD @@ -0,0 +1,36 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageThreadInfoNode", + module_name = "ChatMessageThreadInfoNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift index 4f610e79a9..1fab74cb24 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift @@ -172,25 +172,25 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, })) } -enum ChatMessageThreadInfoType { +public enum ChatMessageThreadInfoType { case bubble(incoming: Bool) case standalone } -class ChatMessageThreadInfoNode: ASDisplayNode { - class Arguments { - let presentationData: ChatPresentationData - let strings: PresentationStrings - let context: AccountContext - let controllerInteraction: ChatControllerInteraction - let type: ChatMessageThreadInfoType - let threadId: Int64 - let parentMessage: Message - let constrainedSize: CGSize - let animationCache: AnimationCache? - let animationRenderer: MultiAnimationRenderer? +public class ChatMessageThreadInfoNode: ASDisplayNode { + public class Arguments { + public let presentationData: ChatPresentationData + public let strings: PresentationStrings + public let context: AccountContext + public let controllerInteraction: ChatControllerInteraction + public let type: ChatMessageThreadInfoType + public let threadId: Int64 + public let parentMessage: Message + public let constrainedSize: CGSize + public let animationCache: AnimationCache? + public let animationRenderer: MultiAnimationRenderer? - init( + public init( presentationData: ChatPresentationData, strings: PresentationStrings, context: AccountContext, @@ -215,7 +215,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { } } - var visibility: Bool = false { + public var visibility: Bool = false { didSet { if self.visibility != oldValue { self.textNode?.visibilityRect = self.visibility ? CGRect.infinite : nil @@ -249,7 +249,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { private var absolutePosition: (CGRect, CGSize)? - override init() { + override public init() { self.contentNode = HighlightTrackingButtonNode() self.contentBackgroundNode = ASImageNode() @@ -298,7 +298,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { self.pressed() } - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame @@ -308,7 +308,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageThreadInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode) { + public class func asyncLayout(_ maybeNode: ChatMessageThreadInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode) { let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode) return { arguments in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD new file mode 100644 index 0000000000..bbe8d60e5f --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageTransitionNode", + module_name = "ChatMessageTransitionNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift new file mode 100644 index 0000000000..eef6515547 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode/Sources/ChatMessageTransitionNode.swift @@ -0,0 +1,15 @@ +import Foundation +import UIKit +import ChatMessageItemView +import AsyncDisplayKit + +public protocol ChatMessageTransitionNodeDecorationItemNode: ASDisplayNode { + var contentView: UIView { get } +} + +public protocol ChatMessageTransitionNode: AnyObject { + typealias DecorationItemNode = ChatMessageTransitionNodeDecorationItemNode + + func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode + func remove(decorationNode: DecorationItemNode) +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD new file mode 100644 index 0000000000..87d31d072a --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageUnsupportedBubbleContentNode", + module_name = "ChatMessageUnsupportedBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift similarity index 80% rename from submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift index 6ffea446a7..d1a9183571 100644 --- a/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -8,11 +8,12 @@ import TelegramCore import TelegramPresentationData import ChatMessageBubbleContentNode import ChatMessageItemCommon +import ChatMessageAttachedContentButtonNode -final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNode { private var buttonNode: ChatMessageAttachedContentButtonNode - required init() { + required public init() { self.buttonNode = ChatMessageAttachedContentButtonNode() super.init() @@ -25,11 +26,11 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode) return { item, layoutConstants, _, _, constrainedSize, _ in @@ -66,7 +67,7 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))? let refinedButtonWidth = max(boundingWidth - insets.left - insets.right, buttonWidth) - let (size, apply) = continueActionButtonLayout(refinedButtonWidth) + let (size, apply) = continueActionButtonLayout(refinedButtonWidth, 33.0) actionButtonSizeAndApply = (size, apply) let adjustedBoundingSize = CGSize(width: refinedButtonWidth + insets.left + insets.right, height: insets.bottom + size.height) @@ -85,23 +86,23 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { if self.buttonNode.frame.contains(point) { return .ignore diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD new file mode 100644 index 0000000000..b702b22424 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD @@ -0,0 +1,38 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageWallpaperBubbleContentNode", + module_name = "ChatMessageWallpaperBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/LocalizedPeerData", + "//submodules/TelegramStringFormatting", + "//submodules/WallpaperBackgroundNode", + "//submodules/PhotoResources", + "//submodules/WallpaperResources", + "//submodules/Markdown", + "//submodules/RadialStatusNode", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AudioTranscriptionPendingIndicatorComponent", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/WallpaperPreviewMedia", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift index c430d8606d..f41cee9a22 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -20,8 +20,9 @@ import ComponentFlow import AudioTranscriptionPendingIndicatorComponent import ChatMessageBubbleContentNode import ChatMessageItemCommon +import WallpaperPreviewMedia -class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { +public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private let mediaBackgroundNode: NavigationBackgroundNode private let subtitleNode: TextNode @@ -40,7 +41,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - required init() { + required public init() { self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) self.mediaBackgroundNode.clipsToBounds = true self.mediaBackgroundNode.cornerRadius = 24.0 @@ -105,7 +106,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -114,7 +115,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { self.statusDisposable.dispose() } - override func didLoad() { + override public func didLoad() { super.didLoad() if #available(iOS 13.0, *) { @@ -132,7 +133,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { item.context.account.pendingPeerMediaUploadManager.cancel(peerId: item.message.id.peerId) } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { @@ -147,7 +148,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { } } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { var mediaHidden = false var currentMedia: Media? if let item = item { @@ -206,7 +207,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { } } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let makeImageLayout = self.imageNode.asyncLayout() let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) @@ -447,7 +448,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { } } - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if let mediaBackgroundContent = self.mediaBackgroundContent { @@ -458,7 +459,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { } } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.statusOverlayNode.alpha > 0.0 { return .none } else if self.mediaBackgroundNode.frame.contains(point) { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD new file mode 100644 index 0000000000..bfa3797d58 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD @@ -0,0 +1,35 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageWebpageBubbleContentNode", + module_name = "ChatMessageWebpageBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/WebsiteType", + "//submodules/InstantPageUI", + "//submodules/UrlHandling", + "//submodules/GalleryData", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/WallpaperPreviewMedia", + "//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift similarity index 92% rename from submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 3ca477a4e4..84c1f85413 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -15,21 +15,24 @@ import GalleryData import TelegramPresentationData import ChatMessageBubbleContentNode import ChatMessageItemCommon +import WallpaperPreviewMedia +import ChatMessageInteractiveMediaNode +import ChatMessageAttachedContentNode private let titleFont: UIFont = Font.semibold(15.0) -final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { private var webPage: TelegramMediaWebpage? - private let contentNode: ChatMessageAttachedContentNode + private var contentNode: ChatMessageAttachedContentNode - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { self.contentNode.visibility = self.visibility } } - required init() { + required public init() { self.contentNode = ChatMessageAttachedContentNode() super.init() @@ -80,6 +83,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { if let webpage = webPageContent { if webpage.story != nil { let _ = item.controllerInteraction.openMessage(item.message, .default) + } else if webpage.instantPage != nil { + strongSelf.contentNode.openMedia?(.default) } else { item.controllerInteraction.openUrl(webpage.url, false, nil, nil) } @@ -94,11 +99,11 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let contentNodeLayout = self.contentNode.asyncLayout() return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -347,6 +352,14 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { break } } + + if let largeMedia = webpage.displayOptions.largeMedia { + if largeMedia { + mediaAndFlags?.1.remove(.preferMediaInline) + } else { + mediaAndFlags?.1.insert(.preferMediaInline) + } + } } else if let adAttribute = item.message.adAttribute { title = nil subtitle = nil @@ -411,27 +424,27 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { + override public func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.contentNode.playMediaWithSound() } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { guard let item = self.item else { return .none } @@ -507,7 +520,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { return .none } - override func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [Media]?) -> Bool { if let media = media { var updatedMedia = media if let current = self.webPage, case let .Loaded(content) = current.content { @@ -539,7 +552,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } - override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } @@ -567,12 +580,12 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { return nil } - override func updateTouchesAtPoint(_ point: CGPoint?) { + override public func updateTouchesAtPoint(_ point: CGPoint?) { let contentNodeFrame = self.contentNode.frame self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) }) } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { return self.contentNode.reactionTargetView(value: value) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD new file mode 100644 index 0000000000..7c7d0d30c1 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatNavigationButton", + module_name = "ChatNavigationButton", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift new file mode 100644 index 0000000000..3feb62f0d8 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatNavigationButton/Sources/ChatNavigationButton.swift @@ -0,0 +1,27 @@ +import Foundation +import UIKit + +public enum ChatNavigationButtonAction: Equatable { + case openChatInfo(expandAvatar: Bool) + case clearHistory + case clearCache + case cancelMessageSelection + case search + case dismiss + case toggleInfoPanel + case spacer +} + +public struct ChatNavigationButton: Equatable { + public let action: ChatNavigationButtonAction + public let buttonItem: UIBarButtonItem + + public init(action: ChatNavigationButtonAction, buttonItem: UIBarButtonItem) { + self.action = action + self.buttonItem = buttonItem + } + + public static func ==(lhs: ChatNavigationButton, rhs: ChatNavigationButton) -> Bool { + return lhs.action == rhs.action && lhs.buttonItem === rhs.buttonItem + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD new file mode 100644 index 0000000000..afcda60459 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD @@ -0,0 +1,54 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatRecentActionsController", + module_name = "ChatRecentActionsController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AccountContext", + "//submodules/AlertUI", + "//submodules/AsyncDisplayKit", + "//submodules/BotPaymentsUI", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl", + "//submodules/TelegramUI/Components/Chat/ChatNavigationButton", + "//submodules/TelegramUI/Components/Chat/ChatLoadingNode", + "//submodules/ChatPresentationInterfaceState", + "//submodules/ContextUI", + "//submodules/Display", + "//submodules/HashtagSearchUI", + "//submodules/InstantPageUI", + "//submodules/InviteLinksUI", + "//submodules/ItemListPeerItem", + "//submodules/ItemListUI", + "//submodules/JoinLinkPreviewUI", + "//submodules/LanguageLinkPreviewUI", + "//submodules/MergeLists", + "//submodules/OpenInExternalAppUI", + "//submodules/Pasteboard", + "//submodules/PeerInfoUI", + "//submodules/Postbox", + "//submodules/PresentationDataUtils", + "//submodules/SearchBarNode", + "//submodules/StickerPackPreviewUI", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramBaseController", + "//submodules/TelegramCallsUI", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TemporaryCachedPeerDataManager", + "//submodules/UndoUI", + "//submodules/WallpaperBackgroundNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatRecentActionsController.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index ce27ec1222..fae2294c6b 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -10,8 +10,9 @@ import AccountContext import AlertUI import PresentationDataUtils import ChatPresentationInterfaceState +import ChatNavigationButton -final class ChatRecentActionsController: TelegramBaseController { +public final class ChatRecentActionsController: TelegramBaseController { private var controllerNode: ChatRecentActionsControllerNode { return self.displayNode as! ChatRecentActionsControllerNode } @@ -21,7 +22,7 @@ final class ChatRecentActionsController: TelegramBaseController { private let initialAdminPeerId: PeerId? private var presentationData: PresentationData private var presentationDataPromise = Promise() - override var updatedPresentationData: (PresentationData, Signal) { + override public var updatedPresentationData: (PresentationData, Signal) { return (self.presentationData, self.presentationDataPromise.get()) } private var presentationDataDisposable: Disposable? @@ -32,7 +33,7 @@ final class ChatRecentActionsController: TelegramBaseController { private let titleView: ChatRecentActionsTitleView - init(context: AccountContext, peer: Peer, adminPeerId: PeerId?) { + public init(context: AccountContext, peer: Peer, adminPeerId: PeerId?) { self.context = context self.peer = peer self.initialAdminPeerId = adminPeerId @@ -74,6 +75,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, updateForwardOptionsState: { _ in }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in + }, presentLinkOptions: { _ in }, shareSelectedMessages: { }, updateTextInputStateAndMode: { _ in }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in @@ -218,7 +220,7 @@ final class ChatRecentActionsController: TelegramBaseController { }) } - required init(coder aDecoder: NSCoder) { + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -239,7 +241,7 @@ final class ChatRecentActionsController: TelegramBaseController { self.controllerNode.updatePresentationData(self.presentationData) } - override func loadDisplayNode() { + override public func loadDisplayNode() { self.displayNode = ChatRecentActionsControllerNode(context: self.context, controller: self, peer: self.peer, presentationData: self.presentationData, interaction: self.interaction, pushController: { [weak self] c in (self?.navigationController as? NavigationController)?.pushViewController(c) }, presentController: { [weak self] c, t, a in @@ -266,7 +268,7 @@ final class ChatRecentActionsController: TelegramBaseController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } - @objc func activateSearch() { + @objc private func activateSearch() { if let navigationBar = self.navigationBar { if !(navigationBar.contentNode is ChatRecentActionsSearchNavigationContentNode) { let searchNavigationNode = ChatRecentActionsSearchNavigationContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, cancel: { [weak self] in diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift similarity index 98% rename from submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 31ebd96f62..1fdad85bfa 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -28,6 +28,8 @@ import ContextUI import Pasteboard import ChatControllerInteraction import ChatPresentationInterfaceState +import ChatMessageItemView +import ChatLoadingNode private final class ChatRecentActionsListOpaqueState { let entries: [ChatRecentActionsEntry] @@ -278,11 +280,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self?.openUrl(url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { - openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) + strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) } }, openWallpaper: { [weak self] message in if let strongSelf = self{ - openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in + strongSelf.context.sharedContext.openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in self?.pushController(c) }) } @@ -1186,3 +1188,17 @@ final class ChatRecentActionsMessageContextExtractedContentSource: ContextExtrac return result } } + +final class ChatMessageContextLocationContentSource: ContextLocationContentSource { + private let controller: ViewController + private let location: CGPoint + + init(controller: ViewController, location: CGPoint) { + self.controller = controller + self.location = location + } + + func transitionInfo() -> ContextControllerLocationViewInfo? { + return ContextControllerLocationViewInfo(location: self.location, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatRecentActionsEmptyNode.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift index 3dbceb060f..0b2e4048a3 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsEmptyNode.swift @@ -10,7 +10,7 @@ import ChatPresentationInterfaceState private let titleFont = Font.medium(16.0) private let textFont = Font.regular(15.0) -final class ChatRecentActionsEmptyNode: ASDisplayNode { +public final class ChatRecentActionsEmptyNode: ASDisplayNode { private var theme: PresentationTheme private var chatWallpaper: TelegramWallpaper @@ -28,7 +28,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode { private var title: String = "" private var text: String = "" - init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatBubbleCorners: PresentationChatBubbleCorners) { + public init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, chatBubbleCorners: PresentationChatBubbleCorners) { self.theme = theme self.chatWallpaper = chatWallpaper @@ -59,7 +59,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode { } } - func updateLayout(presentationData: ChatPresentationData, backgroundNode: WallpaperBackgroundNode, size: CGSize, transition: ContainedViewLayoutTransition) { + public func updateLayout(presentationData: ChatPresentationData, backgroundNode: WallpaperBackgroundNode, size: CGSize, transition: ContainedViewLayoutTransition) { self.wallpaperBackgroundNode = backgroundNode self.layoutParams = (size, presentationData) @@ -119,7 +119,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode { } } - func setup(title: String, text: String) { + public func setup(title: String, text: String) { if self.title != title || self.text != text { self.title = title self.text = text diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift similarity index 100% rename from submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift similarity index 85% rename from submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index 2fbce0e9c5..fa417ad0e6 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -8,6 +8,7 @@ import MergeLists import AccountContext import ChatControllerInteraction import ChatHistoryEntry +import ChatMessageItemImpl enum ChatRecentActionsEntryContentIndex: Int32 { case header = 0 @@ -116,7 +117,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.titleUpdated(title: new) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeAbout(prev, new): var peers = SimpleDictionary() var author: Peer? @@ -147,14 +148,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): var peers = SimpleDictionary() @@ -185,7 +186,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -204,7 +205,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changeUsernames(prev, new): var peers = SimpleDictionary() @@ -235,7 +236,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -273,7 +274,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): var peers = SimpleDictionary() @@ -292,7 +293,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.photoUpdated(image: photo) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleInvites(value): var peers = SimpleDictionary() var author: Peer? @@ -319,7 +320,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleSignatures(value): var peers = SimpleDictionary() var author: Peer? @@ -346,7 +347,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updatePinned(message): switch self.id.contentIndex { case .header: @@ -377,7 +378,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: if let message = message { var peers = SimpleDictionary() @@ -395,7 +396,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { var peers = SimpleDictionary() var author: Peer? @@ -417,7 +418,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } case let .editMessage(prev, message): @@ -462,7 +463,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -479,7 +480,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): switch self.id.contentIndex { @@ -505,7 +506,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -532,7 +533,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: message.id.id), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case .participantJoin, .participantLeave: var peers = SimpleDictionary() @@ -550,7 +551,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantInvite(participant): var peers = SimpleDictionary() var author: Peer? @@ -567,7 +568,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -698,7 +699,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -935,7 +936,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeStickerPack(_, new): var peers = SimpleDictionary() var author: Peer? @@ -964,7 +965,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() var author: Peer? @@ -994,7 +995,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1053,7 +1054,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .pollStopped(message): switch self.id.contentIndex { case .header: @@ -1081,7 +1082,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1098,7 +1099,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): var peers = SimpleDictionary() @@ -1154,7 +1155,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeGeoLocation(_, updated): var peers = SimpleDictionary() var author: Peer? @@ -1176,12 +1177,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case let .updateSlowmode(_, newValue): var peers = SimpleDictionary() @@ -1212,7 +1213,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .startGroupCall, .endGroupCall: var peers = SimpleDictionary() var author: Peer? @@ -1249,7 +1250,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .groupCallUpdateParticipantMuteStatus(participantId, isMuted): var peers = SimpleDictionary() var author: Peer? @@ -1283,7 +1284,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updateGroupCallSettings(joinMuted): var peers = SimpleDictionary() var author: Peer? @@ -1312,7 +1313,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .groupCallUpdateParticipantVolume(participantId, volume): var peers = SimpleDictionary() var author: Peer? @@ -1343,7 +1344,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .deleteExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1369,7 +1370,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .revokeExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1395,7 +1396,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .editExportedInvitation(_, updatedInvite): var peers = SimpleDictionary() var author: Peer? @@ -1421,7 +1422,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantJoinedViaInvite(invite, joinedViaFolderLink): var peers = SimpleDictionary() var author: Peer? @@ -1452,7 +1453,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeHistoryTTL(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1483,7 +1484,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeAvailableReactions(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1525,7 +1526,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeTheme(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1556,7 +1557,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantJoinByRequest(invite, approvedBy): var peers = SimpleDictionary() var author: Peer? @@ -1596,7 +1597,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleCopyProtection(value): var peers = SimpleDictionary() var author: Peer? @@ -1623,7 +1624,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .sendMessage(message): switch self.id.contentIndex { case .header: @@ -1648,7 +1649,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -1665,7 +1666,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case let .createTopic(info): var peers = SimpleDictionary() @@ -1685,7 +1686,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .deleteTopic(info): var peers = SimpleDictionary() var author: Peer? @@ -1706,7 +1707,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .editTopic(prevInfo, newInfo): var peers = SimpleDictionary() var author: Peer? @@ -1785,7 +1786,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .pinTopic(prevInfo, newInfo): var peers = SimpleDictionary() var author: Peer? @@ -1823,7 +1824,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleForum(isForum): var peers = SimpleDictionary() var author: Peer? @@ -1844,7 +1845,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleAntiSpam(isEnabled): var peers = SimpleDictionary() var author: Peer? @@ -1865,7 +1866,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsInteraction.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsInteraction.swift similarity index 100% rename from submodules/TelegramUI/Sources/ChatRecentActionsInteraction.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsInteraction.swift diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift similarity index 100% rename from submodules/TelegramUI/Sources/ChatRecentActionsSearchNavigationContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsTitleView.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsTitleView.swift similarity index 100% rename from submodules/TelegramUI/Sources/ChatRecentActionsTitleView.swift rename to submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsTitleView.swift diff --git a/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD new file mode 100644 index 0000000000..0627218e50 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatSwipeToReplyRecognizer", + module_name = "ChatSwipeToReplyRecognizer", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatSwipeToReplyRecognizer.swift b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/Sources/ChatSwipeToReplyRecognizer.swift similarity index 72% rename from submodules/TelegramUI/Sources/ChatSwipeToReplyRecognizer.swift rename to submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/Sources/ChatSwipeToReplyRecognizer.swift index 9cc8bae71b..a3592134b2 100644 --- a/submodules/TelegramUI/Sources/ChatSwipeToReplyRecognizer.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/Sources/ChatSwipeToReplyRecognizer.swift @@ -1,26 +1,26 @@ import Foundation import UIKit -class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer { - var validatedGesture = false - var firstLocation: CGPoint = CGPoint() - var allowBothDirections: Bool = true +public class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer { + public var validatedGesture = false + public var firstLocation: CGPoint = CGPoint() + public var allowBothDirections: Bool = true - var shouldBegin: (() -> Bool)? + public var shouldBegin: (() -> Bool)? - override init(target: Any?, action: Selector?) { + override public init(target: Any?, action: Selector?) { super.init(target: target, action: action) self.maximumNumberOfTouches = 1 } - override func reset() { + override public func reset() { super.reset() self.validatedGesture = false } - override func touchesBegan(_ touches: Set, with event: UIEvent) { + override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) if let shouldBegin = self.shouldBegin, !shouldBegin() { @@ -31,7 +31,7 @@ class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer { } } - override func touchesMoved(_ touches: Set, with event: UIEvent) { + override public func touchesMoved(_ touches: Set, with event: UIEvent) { let location = touches.first!.location(in: self.view) let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) diff --git a/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD b/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD new file mode 100644 index 0000000000..f36b5f7d00 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "InstantVideoRadialStatusNode", + module_name = "InstantVideoRadialStatusNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/LegacyComponents", + "//submodules/UIKitRuntimeUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift b/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/Sources/InstantVideoRadialStatusNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift rename to submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/Sources/InstantVideoRadialStatusNode.swift index af53ab9b1d..2d8a84006c 100644 --- a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode/Sources/InstantVideoRadialStatusNode.swift @@ -40,7 +40,7 @@ private extension CGPoint { } } -final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDelegate { +public final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDelegate { private let color: UIColor private let hasSeek: Bool private let hapticFeedback = HapticFeedback() @@ -88,7 +88,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele private var statusDisposable: Disposable? private var statusValuePromise = Promise() - var duration: Double? { + public var duration: Double? { if let statusValue = self.statusValue { return statusValue.duration } else { @@ -96,7 +96,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele } } - var status: Signal? { + public var status: Signal? { didSet { if let status = self.status { self.statusValuePromise.set(status |> map { $0 }) @@ -106,12 +106,12 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele } } - var tapGestureRecognizer: UITapGestureRecognizer? - var panGestureRecognizer: UIPanGestureRecognizer? + public var tapGestureRecognizer: UITapGestureRecognizer? + public var panGestureRecognizer: UIPanGestureRecognizer? - var seekTo: ((Double, Bool) -> Void)? + public var seekTo: ((Double, Bool) -> Void)? - init(color: UIColor, hasSeek: Bool) { + public init(color: UIColor, hasSeek: Bool) { self.color = color self.hasSeek = hasSeek @@ -133,7 +133,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele self.statusDisposable?.dispose() } - override func didLoad() { + override public func didLoad() { super.didLoad() guard self.hasSeek else { @@ -149,11 +149,13 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele self.view.addGestureRecognizer(panGestureRecognizer) } - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer === self.tapGestureRecognizer || gestureRecognizer === self.panGestureRecognizer { let center = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0) let location = gestureRecognizer.location(in: self.view) - let distanceFromCenter = location.distanceTo(center) + + let distanceFromCenter = sqrt(pow(location.x - center.x, 2.0) + pow(location.y - center.y, 2.0)) + if distanceFromCenter < self.bounds.width * 0.2 { return false } @@ -256,11 +258,11 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele } } - override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { + override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress, blinkProgress: self.effectiveBlinkProgress, hasSeek: self.hasSeek) } - @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + @objc public override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { let context = UIGraphicsGetCurrentContext()! if !isRasterizing { @@ -286,7 +288,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - let center = bounds.center + let center = CGPoint(x: bounds.midX, y: bounds.midY) context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: bounds.width / 2.0, options: .drawsAfterEndLocation) } } diff --git a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD new file mode 100644 index 0000000000..a06f08dcc1 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ManagedDiceAnimationNode", + module_name = "ManagedDiceAnimationNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AccountContext", + "//submodules/StickerResources", + "//submodules/ManagedAnimationNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift similarity index 93% rename from submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift rename to submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift index 5589c14e15..d08dc54f6f 100644 --- a/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift @@ -8,7 +8,7 @@ import AccountContext import StickerResources import ManagedAnimationNode -enum ManagedDiceAnimationState: Equatable { +public enum ManagedDiceAnimationState: Equatable { case rolling case value(Int32, Bool) } @@ -86,7 +86,7 @@ private struct InteractiveEmojiSuccessParameters { } public struct InteractiveEmojiConfiguration { - static var defaultValue: InteractiveEmojiConfiguration { + public static var defaultValue: InteractiveEmojiConfiguration { return InteractiveEmojiConfiguration(emojis: [], successParameters: [:]) } @@ -98,7 +98,7 @@ public struct InteractiveEmojiConfiguration { self.successParameters = successParameters } - static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration { + public static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration { if let data = appConfiguration.data, let emojis = data["emojies_send_dice"] as? [String] { var successParameters: [String: InteractiveEmojiSuccessParameters] = [:] if let success = data["emojies_send_dice_success"] as? [String: [String: Double]] { @@ -115,7 +115,7 @@ public struct InteractiveEmojiConfiguration { } } -final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStickerNode { +public final class ManagedDiceAnimationNode: ManagedAnimationNode { private let context: AccountContext private let emoji: String @@ -125,9 +125,9 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick private let configuration = Promise() private let emojis = Promise<[TelegramMediaFile]>() - var success: (() -> Void)? + public var success: (() -> Void)? - init(context: AccountContext, emoji: String) { + public init(context: AccountContext, emoji: String) { self.context = context self.emoji = emoji @@ -157,7 +157,7 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick self.disposable.dispose() } - func setState(_ diceState: ManagedDiceAnimationState) { + public func setState(_ diceState: ManagedDiceAnimationState) { let previousState = self.diceState self.diceState = diceState @@ -203,13 +203,13 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick } } - func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { + public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { } - func setFrameIndex(_ frameIndex: Int) { + public func setFrameIndex(_ frameIndex: Int) { } - var currentFrameIndex: Int { + public var currentFrameIndex: Int { return 0 } } diff --git a/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD b/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD new file mode 100644 index 0000000000..499d48c1ab --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/MessageHaptics/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MessageHaptics", + module_name = "MessageHaptics", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/CoffinHaptic.swift b/submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/CoffinHaptic.swift similarity index 91% rename from submodules/TelegramUI/Sources/CoffinHaptic.swift rename to submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/CoffinHaptic.swift index b1e0713ec3..23132043d2 100644 --- a/submodules/TelegramUI/Sources/CoffinHaptic.swift +++ b/submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/CoffinHaptic.swift @@ -5,11 +5,11 @@ import SwiftSignalKit private let firstImpactTime: Double = 0.4 private let secondImpactTime: Double = 0.6 -final class CoffinHaptic: EmojiHaptic { +public final class CoffinHaptic: EmojiHaptic { private var hapticFeedback = HapticFeedback() private var timer: SwiftSignalKit.Timer? private var time: Double = 0.0 - var enabled: Bool = false { + public var enabled: Bool = false { didSet { if !self.enabled { self.reset() @@ -17,9 +17,12 @@ final class CoffinHaptic: EmojiHaptic { } } - var active: Bool { + public var active: Bool { return self.timer != nil } + + public init() { + } private func reset() { if let timer = self.timer { @@ -36,7 +39,7 @@ final class CoffinHaptic: EmojiHaptic { } } - func start(time: Double) { + public func start(time: Double) { self.hapticFeedback.prepareImpact() if time > firstImpactTime { diff --git a/submodules/TelegramUI/Sources/HeartbeatHaptic.swift b/submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/HeartbeatHaptic.swift similarity index 92% rename from submodules/TelegramUI/Sources/HeartbeatHaptic.swift rename to submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/HeartbeatHaptic.swift index e70cc92788..f4a926e8ab 100644 --- a/submodules/TelegramUI/Sources/HeartbeatHaptic.swift +++ b/submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/HeartbeatHaptic.swift @@ -2,18 +2,18 @@ import Foundation import Display import SwiftSignalKit -protocol EmojiHaptic { +public protocol EmojiHaptic { var enabled: Bool { get set } var active: Bool { get } func start(time: Double) } -final class HeartbeatHaptic: EmojiHaptic { +public final class HeartbeatHaptic: EmojiHaptic { private var hapticFeedback = HapticFeedback() private var timer: SwiftSignalKit.Timer? private var time: Double = 0.0 - var enabled: Bool = false { + public var enabled: Bool = false { didSet { if !self.enabled { self.reset() @@ -21,9 +21,12 @@ final class HeartbeatHaptic: EmojiHaptic { } } - var active: Bool { + public var active: Bool { return self.timer != nil } + + public init() { + } private func reset() { if let timer = self.timer { @@ -42,7 +45,7 @@ final class HeartbeatHaptic: EmojiHaptic { } } - func start(time: Double) { + public func start(time: Double) { self.hapticFeedback.prepareImpact() if time > 2.0 { diff --git a/submodules/TelegramUI/Sources/PeachHaptic.swift b/submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/PeachHaptic.swift similarity index 91% rename from submodules/TelegramUI/Sources/PeachHaptic.swift rename to submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/PeachHaptic.swift index 10a6d757ab..c68b34f57e 100644 --- a/submodules/TelegramUI/Sources/PeachHaptic.swift +++ b/submodules/TelegramUI/Components/Chat/MessageHaptics/Sources/PeachHaptic.swift @@ -4,11 +4,11 @@ import SwiftSignalKit private let impactTime: Double = 0.6 -final class PeachHaptic: EmojiHaptic { +public final class PeachHaptic: EmojiHaptic { private var hapticFeedback = HapticFeedback() private var timer: SwiftSignalKit.Timer? private var time: Double = 0.0 - var enabled: Bool = false { + public var enabled: Bool = false { didSet { if !self.enabled { self.reset() @@ -16,9 +16,12 @@ final class PeachHaptic: EmojiHaptic { } } - var active: Bool { + public var active: Bool { return self.timer != nil } + + public init() { + } private func reset() { if let timer = self.timer { @@ -35,7 +38,7 @@ final class PeachHaptic: EmojiHaptic { } } - func start(time: Double) { + public func start(time: Double) { self.hapticFeedback.prepareImpact() if time > impactTime { diff --git a/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD b/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD new file mode 100644 index 0000000000..1cc5965699 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PollBubbleTimerNode", + module_name = "PollBubbleTimerNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/PollBubbleTimerNode.swift b/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/Sources/PollBubbleTimerNode.swift similarity index 97% rename from submodules/TelegramUI/Sources/PollBubbleTimerNode.swift rename to submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/Sources/PollBubbleTimerNode.swift index 6d59a76864..646897984a 100644 --- a/submodules/TelegramUI/Sources/PollBubbleTimerNode.swift +++ b/submodules/TelegramUI/Components/Chat/PollBubbleTimerNode/Sources/PollBubbleTimerNode.swift @@ -39,7 +39,7 @@ private struct ContentParticle { } } -final class PollBubbleTimerNode: ASDisplayNode { +public final class PollBubbleTimerNode: ASDisplayNode { private struct Params: Equatable { var regularColor: UIColor var proximityColor: UIColor @@ -58,9 +58,9 @@ final class PollBubbleTimerNode: ASDisplayNode { private var currentParams: Params? - var reachedTimeout: (() -> Void)? + public var reachedTimeout: (() -> Void)? - override init() { + override public init() { var updateInHierarchy: ((Bool) -> Void)? self.hierarchyTrackingNode = HierarchyTrackingNode({ value in updateInHierarchy?(value) @@ -89,7 +89,7 @@ final class PollBubbleTimerNode: ASDisplayNode { self.animator?.invalidate() } - func update(regularColor: UIColor, proximityColor: UIColor, timeout: Int32, deadlineTimestamp: Int32?) { + public func update(regularColor: UIColor, proximityColor: UIColor, timeout: Int32, deadlineTimestamp: Int32?) { let params = Params( regularColor: regularColor, proximityColor: proximityColor, diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 7046ba6e7f..8ee9d2fb4a 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -362,14 +362,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { let forwardOptions: Signal forwardOptions = strongSelf.presentationInterfaceStatePromise.get() |> map { state -> ChatControllerSubject.ForwardOptions in - return ChatControllerSubject.ForwardOptions(hideNames: state.interfaceState.forwardOptionsState?.hideNames ?? false, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false, replyOptions: nil) + return ChatControllerSubject.ForwardOptions(hideNames: state.interfaceState.forwardOptionsState?.hideNames ?? false, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false) } |> distinctUntilChanged let chatController = strongSelf.context.sharedContext.makeChatController( context: strongSelf.context, chatLocation: .peer(id: strongSelf.context.account.peerId), - subject: .messageOptions(peerIds: peerIds, ids: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], info: ChatControllerSubject.MessageOptionsInfo(kind: .forward), options: forwardOptions), + subject: .messageOptions(peerIds: peerIds, ids: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], info: .forward(ChatControllerSubject.MessageOptionsInfo.Forward(options: forwardOptions))), botStart: nil, mode: .standard(previewing: true) ) @@ -544,6 +544,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { contextController.immediateItemsTransitionAnimation = true strongSelf.controller?.presentInGlobalOverlay(contextController) }, presentReplyOptions: { _ in + }, presentLinkOptions: { _ in }, shareSelectedMessages: { }, updateTextInputStateAndMode: { [weak self] f in if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index fc37c57798..c6fa45b0f2 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -18,6 +18,14 @@ public final class TabSelectorComponent: Component { } } + public struct CustomLayout: Equatable { + public var spacing: CGFloat + + public init(spacing: CGFloat) { + self.spacing = spacing + } + } + public struct Item: Equatable { public var id: AnyHashable public var title: String @@ -32,17 +40,20 @@ public final class TabSelectorComponent: Component { } public let colors: Colors + public let customLayout: CustomLayout? public let items: [Item] public let selectedId: AnyHashable? public let setSelectedId: (AnyHashable) -> Void public init( colors: Colors, + customLayout: CustomLayout? = nil, items: [Item], selectedId: AnyHashable?, setSelectedId: @escaping (AnyHashable) -> Void ) { self.colors = colors + self.customLayout = customLayout self.items = items self.selectedId = selectedId self.setSelectedId = setSelectedId @@ -52,6 +63,9 @@ public final class TabSelectorComponent: Component { if lhs.colors != rhs.colors { return false } + if lhs.customLayout != rhs.customLayout { + return false + } if lhs.items != rhs.items { return false } @@ -96,7 +110,14 @@ public final class TabSelectorComponent: Component { let baseHeight: CGFloat = 28.0 let innerInset: CGFloat = 12.0 - let spacing: CGFloat = 2.0 + let spacing: CGFloat = component.customLayout?.spacing ?? 2.0 + + let itemFont: UIFont + if component.customLayout != nil { + itemFont = Font.medium(14.0) + } else { + itemFont = Font.semibold(14.0) + } if self.selectionView.image == nil { self.selectionView.image = generateStretchableFilledCircleImage(diameter: baseHeight, color: component.colors.selection) @@ -123,7 +144,7 @@ public final class TabSelectorComponent: Component { let itemSize = itemView.title.update( transition: .immediate, component: AnyComponent(PlainButtonComponent( - content: AnyComponent(Text(text: item.title, font: Font.semibold(14.0), color: component.colors.foreground)), + content: AnyComponent(Text(text: item.title, font: itemFont, color: component.colors.foreground)), effectAlignment: .center, minSize: nil, action: { [weak self] in diff --git a/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD b/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD new file mode 100644 index 0000000000..2bbdb275af --- /dev/null +++ b/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "WallpaperPreviewMedia", + module_name = "WallpaperPreviewMedia", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/TelegramCore", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift b/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift similarity index 70% rename from submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift rename to submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift index 0a232c2355..7903e414e8 100644 --- a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift +++ b/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift @@ -3,7 +3,7 @@ import UIKit import Postbox import TelegramCore -enum WallpaperPreviewMediaContent: Equatable { +public enum WallpaperPreviewMediaContent: Equatable { case file(file: TelegramMediaFile, colors: [UInt32], rotation: Int32?, intensity: Int32?, Bool, Bool) case image(representations: [TelegramMediaImageRepresentation]) case color(UIColor) @@ -11,23 +11,23 @@ enum WallpaperPreviewMediaContent: Equatable { case themeSettings(TelegramThemeSettings) } -final class WallpaperPreviewMedia: Media { - var id: MediaId? { +public final class WallpaperPreviewMedia: Media { + public var id: MediaId? { return nil } - let peerIds: [PeerId] = [] + public let peerIds: [PeerId] = [] - let content: WallpaperPreviewMediaContent + public let content: WallpaperPreviewMediaContent - init(content: WallpaperPreviewMediaContent) { + public init(content: WallpaperPreviewMediaContent) { self.content = content } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { self.content = .color(.clear) } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { } public func isEqual(to other: Media) -> Bool { @@ -47,7 +47,13 @@ final class WallpaperPreviewMedia: Media { } } -extension WallpaperPreviewMedia { +private extension UIColor { + convenience init(rgb: UInt32) { + self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) + } +} + +public extension WallpaperPreviewMedia { convenience init?(wallpaper: TelegramWallpaper) { switch wallpaper { case let .color(color): diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json index 8a22af5245..43739ae6ba 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json @@ -1,22 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ConversationInstantPageButtonIconIncoming@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "ConversationInstantPageButtonIconIncoming@3x.png", - "scale" : "3x" + "filename" : "InstantIcon.svg", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@2x.png b/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@2x.png deleted file mode 100644 index 88d8b6e0a3..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@3x.png b/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@3x.png deleted file mode 100644 index 4797137de8..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/InstantIcon.svg b/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/InstantIcon.svg new file mode 100644 index 0000000000..dd41340826 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/InstantIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 4cfdeb4c8e..cbf55e6255 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -12,11 +12,80 @@ import ContextUI import ChatInterfaceState import PresentationDataUtils import ChatMessageTextBubbleContentNode +import TextFormat +import ChatMessageItemView +import ChatMessageBubbleItemNode -func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) { - guard let peerId = selfController.chatLocation.peerId else { +private enum OptionsId: Hashable { + case reply + case forward + case link +} + +private func presentChatInputOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, initialId: OptionsId) { + var getContextController: (() -> ContextController?)? + + var sources: [ContextController.Source] = [] + + let replySelectionState = Promise(ChatControllerSubject.MessageOptionsInfo.SelectionState(quote: nil)) + + if let source = chatReplyOptions(selfController: selfController, sourceNode: sourceNode, getContextController: { + return getContextController?() + }, selectionState: replySelectionState) { + sources.append(source) + } + + var forwardDismissedForCancel: (() -> Void)? + if let (source, dismissedForCancel) = chatForwardOptions(selfController: selfController, sourceNode: sourceNode, getContextController: { + return getContextController?() + }) { + forwardDismissedForCancel = dismissedForCancel + sources.append(source) + } + + if let source = chatLinkOptions(selfController: selfController, sourceNode: sourceNode, getContextController: { + return getContextController?() + }, replySelectionState: replySelectionState) { + sources.append(source) + } + + if sources.isEmpty { return } + + selfController.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + + selfController.canReadHistory.set(false) + + let contextController = ContextController( + presentationData: selfController.presentationData, + configuration: ContextController.Configuration( + sources: sources, + initialId: AnyHashable(initialId) + ) + ) + contextController.dismissed = { [weak selfController] in + selfController?.canReadHistory.set(true) + } + + getContextController = { [weak contextController] in + return contextController + } + + contextController.dismissedForCancel = { + forwardDismissedForCancel?() + } + + selfController.presentInGlobalOverlay(contextController) +} + +private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?) -> (ContextController.Source, () -> Void)? { + guard let peerId = selfController.chatLocation.peerId else { + return nil + } + guard let initialForwardMessageIds = selfController.presentationInterfaceState.interfaceState.forwardMessageIds, !initialForwardMessageIds.isEmpty else { + return nil + } let presentationData = selfController.presentationData let forwardOptions = selfController.presentationInterfaceStatePromise.get() @@ -25,11 +94,11 @@ func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: A if peerId.namespace == Namespaces.Peer.SecretChat { hideNames = true } - return ChatControllerSubject.ForwardOptions(hideNames: hideNames, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false, replyOptions: nil) + return ChatControllerSubject.ForwardOptions(hideNames: hideNames, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false) } |> distinctUntilChanged - let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [peerId], ids: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], info: ChatControllerSubject.MessageOptionsInfo(kind: .forward), options: forwardOptions), botStart: nil, mode: .standard(previewing: true)) + let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [peerId], ids: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], info: .forward(ChatControllerSubject.MessageOptionsInfo.Forward(options: forwardOptions))), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) let messageIds = selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] @@ -201,15 +270,7 @@ func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: A return items } - selfController.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() - - selfController.canReadHistory.set(false) - - let contextController = ContextController(presentationData: selfController.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(content: .list($0)) }) - contextController.dismissed = { [weak selfController] in - selfController?.canReadHistory.set(true) - } - contextController.dismissedForCancel = { [weak selfController, weak chatController] in + let dismissedForCancel: () -> Void = { [weak selfController, weak chatController] in guard let selfController else { return } @@ -219,8 +280,18 @@ func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: A selfController.updateChatPresentationInterfaceState(interactive: false, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) }) } } - contextController.immediateItemsTransitionAnimation = true - selfController.presentInGlobalOverlay(contextController) + + //TODO:localize + return (ContextController.Source( + id: AnyHashable(OptionsId.forward), + title: "Forward", + source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), + items: items |> map { ContextController.Items(content: .list($0)) } + ), dismissedForCancel) +} + +func presentChatForwardOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) { + presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .forward) } private func generateChatReplyOptionItems(selfController: ChatControllerImpl, chatController: ChatControllerImpl) -> Signal { @@ -228,12 +299,9 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch return .complete() } - let messageIds: [EngineMessage.Id] = [replySubject.messageId] - let messagesCount: Signal = .single(1) - - let items = combineLatest(selfController.context.account.postbox.messagesAtIds(messageIds), messagesCount) + let items = selfController.context.account.postbox.messagesAtIds([replySubject.messageId]) |> deliverOnMainQueue - |> map { [weak selfController, weak chatController] messages, messagesCount -> [ContextMenuItem] in + |> map { [weak selfController, weak chatController] messages -> [ContextMenuItem] in guard let selfController, let chatController else { return [] } @@ -297,12 +365,8 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch subItems.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { [weak selfController, weak chatController] c, _ in - guard let selfController, let chatController else { - return - } - c.setItems(generateChatReplyOptionItems(selfController: selfController, chatController: chatController), minHeight: nil, previousActionsTransition: .slide(forward: false)) - //c.popItems() + }, iconPosition: .left, action: { c, _ in + c.popItems() }))) subItems.append(.separator) @@ -322,11 +386,12 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch f(.default) }))) - //c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - - let minHeight = c.getActionsMinHeight() - c.immediateItemsTransitionAnimation = false - c.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: minHeight, previousActionsTransition: .slide(forward: true)) + c.pushItems(items: .single(ContextController.Items(content: .list(subItems), dismissed: { [weak contentNode] in + guard let contentNode else { + return + } + contentNode.cancelTextSelection() + }))) break } @@ -371,45 +436,29 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch return items |> map { ContextController.Items(content: .list($0), tip: tip) } } -func presentChatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) { +private func chatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?, selectionState: Promise) -> ContextController.Source? { guard let peerId = selfController.chatLocation.peerId else { - return + return nil } guard let replySubject = selfController.presentationInterfaceState.interfaceState.replyMessageSubject else { - return + return nil } - let replyOptionsSubject = Promise() - replyOptionsSubject.set(.single(ChatControllerSubject.ForwardOptions(hideNames: false, hideCaptions: false, replyOptions: ChatControllerSubject.ReplyOptions(hasQuote: replySubject.quote != nil)))) - - //let presentationData = selfController.presentationData - - var replyQuote: ChatControllerSubject.MessageOptionsInfo.ReplyQuote? + var replyQuote: ChatControllerSubject.MessageOptionsInfo.Quote? if let quote = replySubject.quote { - replyQuote = ChatControllerSubject.MessageOptionsInfo.ReplyQuote(messageId: replySubject.messageId, text: quote.text) + replyQuote = ChatControllerSubject.MessageOptionsInfo.Quote(messageId: replySubject.messageId, text: quote.text) } - guard let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [replySubject.messageId.peerId], ids: [replySubject.messageId], info: ChatControllerSubject.MessageOptionsInfo(kind: .reply(initialQuote: replyQuote)), options: replyOptionsSubject.get()), botStart: nil, mode: .standard(previewing: true)) as? ChatControllerImpl else { - return + selectionState.set(.single(ChatControllerSubject.MessageOptionsInfo.SelectionState(quote: replyQuote))) + + guard let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [replySubject.messageId.peerId], ids: [replySubject.messageId], info: .reply(ChatControllerSubject.MessageOptionsInfo.Reply(quote: replyQuote, selectionState: selectionState))), botStart: nil, mode: .standard(previewing: true)) as? ChatControllerImpl else { + return nil } chatController.canReadHistory.set(false) let items = generateChatReplyOptionItems(selfController: selfController, chatController: chatController) - selfController.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() - - selfController.canReadHistory.set(false) - - let contextController = ContextController(presentationData: selfController.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items) - contextController.dismissed = { [weak selfController] in - selfController?.canReadHistory.set(true) - } - contextController.dismissedForCancel = { - } - contextController.immediateItemsTransitionAnimation = true - selfController.presentInGlobalOverlay(contextController) - - chatController.performTextSelectionAction = { [weak selfController, weak contextController] message, canCopy, text, action in - guard let selfController, let contextController else { + chatController.performTextSelectionAction = { [weak selfController] message, canCopy, text, action in + guard let selfController, let contextController = getContextController() else { return } @@ -417,6 +466,18 @@ func presentChatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASD selfController.controllerInteraction?.performTextSelectionAction(message, canCopy, text, action) } + + //TODO:localize + return ContextController.Source( + id: AnyHashable(OptionsId.reply), + title: "Reply", + source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), + items: items + ) +} + +func presentChatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) { + presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .reply) } func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubject: ChatInterfaceState.ReplyMessageSubject) { @@ -537,3 +598,204 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj selfController.effectiveNavigationController?.pushViewController(controller) }) } + +private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?, replySelectionState: Promise) -> ContextController.Source? { + guard let peerId = selfController.chatLocation.peerId else { + return nil + } + guard let initialUrlPreview = selfController.presentationInterfaceState.urlPreview else { + return nil + } + + let linkOptions = combineLatest(queue: .mainQueue(), + selfController.presentationInterfaceStatePromise.get(), + replySelectionState.get() + ) + |> map { state, replySelectionState -> ChatControllerSubject.LinkOptions in + let urlPreview = state.urlPreview ?? initialUrlPreview + + var webpageOptions: TelegramMediaWebpageDisplayOptions = .default + + if let (_, webpage) = state.urlPreview, case let .Loaded(content) = webpage.content { + webpageOptions = content.displayOptions + } + + return ChatControllerSubject.LinkOptions( + messageText: state.interfaceState.composeInputState.inputText.string, + messageEntities: generateChatInputTextEntities(state.interfaceState.composeInputState.inputText, generateLinks: true), + replyMessageId: state.interfaceState.replyMessageSubject?.messageId, + replyQuote: replySelectionState.quote?.text, + url: urlPreview.0, + webpage: urlPreview.1, + linkBelowText: webpageOptions.position != .aboveText, + largeMedia: webpageOptions.largeMedia != false + ) + } + |> distinctUntilChanged + + guard let chatController = selfController.context.sharedContext.makeChatController(context: selfController.context, chatLocation: .peer(id: peerId), subject: .messageOptions(peerIds: [peerId], ids: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], info: .link(ChatControllerSubject.MessageOptionsInfo.Link(options: linkOptions))), botStart: nil, mode: .standard(previewing: true)) as? ChatControllerImpl else { + return nil + } + chatController.canReadHistory.set(false) + + let items = linkOptions + |> deliverOnMainQueue + |> map { [weak selfController] linkOptions -> [ContextMenuItem] in + guard let selfController else { + return [] + } + var items: [ContextMenuItem] = [] + + if "".isEmpty { + //TODO:localize + + items.append(.action(ContextMenuActionItem(text: "Above the Message", icon: { theme in + if linkOptions.linkBelowText { + return nil + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak selfController] _, f in + selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + guard var urlPreview = state.urlPreview else { + return state + } + if case let .Loaded(content) = urlPreview.1.content { + var displayOptions = content.displayOptions + displayOptions.position = .aboveText + urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions)))) + } + return state.updatedUrlPreview(urlPreview) + }) + }))) + + items.append(.action(ContextMenuActionItem(text: "Below the Message", icon: { theme in + if !linkOptions.linkBelowText { + return nil + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak selfController] _, f in + selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + guard var urlPreview = state.urlPreview else { + return state + } + if case let .Loaded(content) = urlPreview.1.content { + var displayOptions = content.displayOptions + displayOptions.position = .belowText + urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions)))) + } + return state.updatedUrlPreview(urlPreview) + }) + }))) + } + + if "".isEmpty { + if !items.isEmpty { + items.append(.separator) + } + + //TODO:localize + + items.append(.action(ContextMenuActionItem(text: "Smaller Media", icon: { theme in + if linkOptions.largeMedia { + return nil + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak selfController] _, f in + selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + guard var urlPreview = state.urlPreview else { + return state + } + if case let .Loaded(content) = urlPreview.1.content { + var displayOptions = content.displayOptions + displayOptions.largeMedia = false + urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions)))) + } + return state.updatedUrlPreview(urlPreview) + }) + }))) + + items.append(.action(ContextMenuActionItem(text: "Larger Media", icon: { theme in + if !linkOptions.largeMedia { + return nil + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak selfController] _, f in + selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + guard var urlPreview = state.urlPreview else { + return state + } + if case let .Loaded(content) = urlPreview.1.content { + var displayOptions = content.displayOptions + displayOptions.largeMedia = true + urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions)))) + } + return state.updatedUrlPreview(urlPreview) + }) + }))) + } + + if !items.isEmpty { + items.append(.separator) + } + + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Remove Link Preview", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController, weak chatController] c, f in + guard let selfController else { + return + } + + selfController.chatDisplayNode.dismissUrlPreview() + + let _ = chatController + + f(.default) + }))) + + return items + } + + chatController.performOpenURL = { [weak selfController] message, url in + guard let selfController else { + return + } + + //TODO: + //func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: String?) -> (String?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? { + if let (updatedUrlPreviewUrl, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewUrl { + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak selfController] result in + guard let selfController else { + return + } + + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in + if let webpage = result(nil), var urlPreview = state.urlPreview { + if case let .Loaded(content) = urlPreview.1.content, case let .Loaded(newContent) = webpage.content { + urlPreview = (updatedUrlPreviewUrl, TelegramMediaWebpage(webpageId: webpage.webpageId, content: .Loaded(newContent.withDisplayOptions(content.displayOptions)))) + } + + return state.updatedUrlPreview(urlPreview) + } else { + return state + } + }) + }) + } + } + + //TODO:localize + return ContextController.Source( + id: AnyHashable(OptionsId.link), + title: "Link", + source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), + items: items |> map { ContextController.Items(content: .list($0)) } + ) +} + +func presentChatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) { + presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .link) +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 299841c627..dbad588d76 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -106,6 +106,14 @@ import SaveToCameraRoll import ChatMessageDateAndStatusNode import ReplyAccessoryPanelNode import TextSelectionNode +import ChatMessagePollBubbleContentNode +import ChatMessageItem +import ChatMessageItemImpl +import ChatMessageItemView +import ChatMessageItemCommon +import ChatMessageAnimatedStickerItemNode +import ChatMessageBubbleItemNode +import ChatNavigationButton public enum ChatControllerPeekActions { case standard @@ -545,6 +553,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var storyStats: PeerStoryStats? var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? + var performOpenURL: ((Message?, String) -> Void)? public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) { let _ = ChatControllerCount.modify { value in @@ -1336,7 +1345,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openPeer: { [weak self] peer, navigation, fromMessage, source in var expandAvatar = false if case let .groupParticipant(storyStats, avatarHeaderNode) = source { - if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNode { + if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNodeImpl { self?.openStories(peerId: peer.id, avatarHeaderNode: avatarHeaderNode, avatarNode: nil) return } else { @@ -2745,7 +2754,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId) } - strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message) + if let performOpenURL = strongSelf.performOpenURL { + performOpenURL(message, url) + } else { + strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message) + } } }, shareCurrentLocation: { [weak self] in if let strongSelf = self { @@ -2840,7 +2853,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { strongSelf.chatDisplayNode.dismissInput() - openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) + strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) if case .overlay = strongSelf.presentationInterfaceState.mode { strongSelf.chatDisplayNode.dismissAsOverlay() @@ -2851,7 +2864,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { strongSelf.chatDisplayNode.dismissInput() - openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in + strongSelf.context.sharedContext.openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in self?.push(c) }) }) @@ -3937,35 +3950,42 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G f() } case let .quote(range): - if let currentContextController = strongSelf.currentContextController { - currentContextController.dismiss(completion: { - }) + let completion: (ContainedViewLayoutTransition?) -> Void = { transition in + guard let self else { + return + } + if let currentContextController = self.currentContextController { + self.currentContextController = nil + + if let transition { + currentContextController.dismissWithCustomTransition(transition: transition) + } else { + currentContextController.dismiss(completion: {}) + } + } } - - let completion: (ContainedViewLayoutTransition) -> Void = { _ in } - if let messageId = message?.id { + if let messageId = message?.id, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + var quoteData: EngineMessageReplyQuote? + + let quoteText = (message.text as NSString).substring(with: NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound)) + quoteData = EngineMessageReplyQuote(text: quoteText, entities: []) + + let replySubject = ChatInterfaceState.ReplyMessageSubject( + messageId: message.id, + quote: quoteData + ) + if canSendMessagesToChat(strongSelf.presentationInterfaceState) { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { - var quoteData: EngineMessageReplyQuote? - - let quoteText = (message.text as NSString).substring(with: NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound)) - quoteData = EngineMessageReplyQuote(text: quoteText, entities: []) - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject( - messageId: message.id, - quote: quoteData - )) }).updatedSearch(nil).updatedShowCommands(false) }, completion: completion) - strongSelf.updateItemNodesSearchTextHighlightStates() - strongSelf.chatDisplayNode.ensureInputViewFocused() - } else { - completion(.immediate) - } + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject) }).updatedSearch(nil).updatedShowCommands(false) }, completion: completion) + strongSelf.updateItemNodesSearchTextHighlightStates() + strongSelf.chatDisplayNode.ensureInputViewFocused() }, alertAction: { - completion(.immediate) + completion(nil) }, delay: true) } else { - completion(.immediate) + moveReplyMessageToAnotherChat(selfController: strongSelf, replySubject: replySubject) + completion(nil) } } else { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil) }) }, completion: completion) @@ -4997,6 +5017,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G //} self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) + + if case .messageOptions = self.subject { + self.chatTitleView?.disableAnimations = true + } + self.navigationItem.titleView = self.chatTitleView self.chatTitleView?.longPressed = { [weak self] in if let strongSelf = self, let peerView = strongSelf.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { @@ -5197,7 +5222,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return message?.totalCount } |> distinctUntilChanged - } else if case let .messageOptions(peerIds, messageIds, info, options) = subject { + } else if case let .messageOptions(peerIds, messageIds, info) = subject { displayedCountSignal = self.presentationInterfaceStatePromise.get() |> map { state -> Int? in if let selectionState = state.interfaceState.selectionState { @@ -5213,9 +5238,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let presentationData = self.presentationData - switch info.kind { - case .forward: - subtitleTextSignal = combineLatest(peers, options, displayedCountSignal) + switch info { + case let .forward(forward): + subtitleTextSignal = combineLatest(peers, forward.options, displayedCountSignal) |> map { peersView, options, count in let peers = peersView.peers.values if !peers.isEmpty { @@ -5286,6 +5311,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .reply: //TODO:localize subtitleTextSignal = .single("You can select a specific part to quote") + case .link: + //TODO:localize + subtitleTextSignal = .single("Tap on a link to generate its preview") } } @@ -5298,9 +5326,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { hasPeerInfo = .single(true) } + + enum MessageOptionsTitleInfo { + case reply(hasQuote: Bool) + } + let messageOptionsTitleInfo: Signal + if case let .messageOptions(_, _, info) = self.subject { + switch info { + case .forward, .link: + messageOptionsTitleInfo = .single(nil) + case let .reply(reply): + messageOptionsTitleInfo = reply.selectionState.get() + |> map { selectionState -> Bool in + return selectionState.quote != nil + } + |> distinctUntilChanged + |> map { hasQuote -> MessageOptionsTitleInfo in + return .reply(hasQuote: hasQuote) + } + } + } else { + messageOptionsTitleInfo = .single(nil) + } - self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo) - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo in + self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo, messageOptionsTitleInfo) + |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo, messageOptionsTitleInfo in if let strongSelf = self { var isScheduledMessages = false if case .scheduledMessages = presentationInterfaceState.subject { @@ -5308,14 +5358,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let peer = peerViewMainPeer(peerView) { - if case let .messageOptions(_, _, info, _) = presentationInterfaceState.subject { - if case let .reply(initialQuote) = info.kind { + if case let .messageOptions(_, _, info) = presentationInterfaceState.subject { + if case .reply = info { //TODO:localize - if initialQuote != nil { + if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote { strongSelf.chatTitleView?.titleContent = .custom("Reply to Quote", subtitleText, false) } else { strongSelf.chatTitleView?.titleContent = .custom("Reply to Message", subtitleText, false) } + } else if case .link = info { + //TODO:localize + strongSelf.chatTitleView?.titleContent = .custom("Link Preview Settings", subtitleText, false) } else if displayedCount == 1 { strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false) } else { @@ -6650,7 +6703,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - if case let .messageOptions(_, messageIds, _, _) = self.subject, messageIds.count > 1 { + if case let .messageOptions(_, messageIds, _) = self.subject, messageIds.count > 1 { self.updateChatPresentationInterfaceState(interactive: false, { state in return state.updatedInterfaceState({ $0.withUpdatedSelectedMessages(messageIds) }) }) @@ -6695,6 +6748,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G info.1.dispose() } self.urlPreviewQueryState?.1.dispose() + self.editingUrlPreviewQueryState?.1.dispose() self.audioRecorderDisposable?.dispose() self.audioRecorderStatusDisposable?.dispose() self.videoRecorderDisposable?.dispose() @@ -8028,9 +8082,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { isScheduledMessages = false } - let duration: Double = strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? ChatMessageTransitionNode.animationDuration : 0.18 - let curve: ContainedViewLayoutTransitionCurve = strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? ChatMessageTransitionNode.verticalAnimationCurve : .easeInOut - let controlPoints: (Float, Float, Float, Float) = strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? ChatMessageTransitionNode.verticalAnimationControlPoints : (0.5, 0.33, 0.0, 0.0) + let duration: Double = strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? ChatMessageTransitionNodeImpl.animationDuration : 0.18 + let curve: ContainedViewLayoutTransitionCurve = strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? ChatMessageTransitionNodeImpl.verticalAnimationCurve : .easeInOut + let controlPoints: (Float, Float, Float, Float) = strongSelf.chatDisplayNode.messageTransitionNode.hasScheduledTransitions ? ChatMessageTransitionNodeImpl.verticalAnimationControlPoints : (0.5, 0.33, 0.0, 0.0) let shouldUseFastMessageSendAnimation = strongSelf.chatDisplayNode.shouldUseFastMessageSendAnimation @@ -8841,6 +8895,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } presentChatReplyOptions(selfController: self, sourceNode: sourceNode) + }, presentLinkOptions: { [weak self] sourceNode in + guard let self else { + return + } + presentChatLinkOptions(selfController: self, sourceNode: sourceNode) }, shareSelectedMessages: { [weak self] in if let strongSelf = self, let selectedIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty { strongSelf.commitPurposefulAction() @@ -9744,7 +9803,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) }))) - contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil) + contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) } return } else { @@ -9763,7 +9822,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) }))) - contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil) + contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) return } else { @@ -11913,16 +11972,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { - switch self.presentationInterfaceState.mode { - case let .standard(previewing): - if previewing { - if let subject = self.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind { - return self.chatDisplayNode.preferredContentSizeForLayout(layout) - } - } - default: - break - } return nil } @@ -15477,15 +15526,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if addedTransitions.count > 1 { - var transitions: [(Int64, ChatMessageTransitionNode.Source, () -> Void)] = [] + var transitions: [(Int64, ChatMessageTransitionNodeImpl.Source, () -> Void)] = [] for (correlationId, uniqueIds, initiated) in addedTransitions { - var source: ChatMessageTransitionNode.Source? + var source: ChatMessageTransitionNodeImpl.Source? if uniqueIds.count > 1 { - source = .groupedMediaInput(ChatMessageTransitionNode.Source.GroupedMediaInput(extractSnapshots: { + source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: { return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) }) })) } else if let uniqueId = uniqueIds.first { - source = .mediaInput(ChatMessageTransitionNode.Source.MediaInput(extractSnapshot: { + source = .mediaInput(ChatMessageTransitionNodeImpl.Source.MediaInput(extractSnapshot: { return getAnimatedTransitionSource?(uniqueId) })) } @@ -15495,13 +15544,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.messageTransitionNode.add(grouped: transitions) } else if let (correlationId, uniqueIds, initiated) = addedTransitions.first { - var source: ChatMessageTransitionNode.Source? + var source: ChatMessageTransitionNodeImpl.Source? if uniqueIds.count > 1 { - source = .groupedMediaInput(ChatMessageTransitionNode.Source.GroupedMediaInput(extractSnapshots: { + source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: { return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) }) })) } else if let uniqueId = uniqueIds.first { - source = .mediaInput(ChatMessageTransitionNode.Source.MediaInput(extractSnapshot: { + source = .mediaInput(ChatMessageTransitionNodeImpl.Source.MediaInput(extractSnapshot: { return getAnimatedTransitionSource?(uniqueId) })) } @@ -15769,7 +15818,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let extractedView = videoController.extractVideoSnapshot() { usedCorrelationId = true - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .videoMessage(ChatMessageTransitionNode.Source.VideoMessage(view: extractedView)), initiated: { [weak videoController] in + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .videoMessage(ChatMessageTransitionNodeImpl.Source.VideoMessage(view: extractedView)), initiated: { [weak videoController] in videoController?.hideVideoSnapshot() guard let strongSelf = self else { return @@ -15881,7 +15930,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { usedCorrelationId = true - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNode.Source.AudioMicInput(micButton: micButton)), initiated: { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: { guard let strongSelf = self else { return } @@ -17347,7 +17396,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - func openStories(peerId: EnginePeer.Id, avatarHeaderNode: ChatMessageAvatarHeaderNode?, avatarNode: AvatarNode?) { + func openStories(peerId: EnginePeer.Id, avatarHeaderNode: ChatMessageAvatarHeaderNodeImpl?, avatarNode: AvatarNode?) { if let avatarNode = avatarHeaderNode?.avatarNode ?? avatarNode { StoryContainerScreen.openPeerStories(context: self.context, peerId: peerId, parentController: self, avatarNode: avatarNode) } @@ -18006,7 +18055,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if canDisplayContextMenu, let contextController = contextController { - contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil) + contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) } else { actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in @@ -19441,58 +19490,6 @@ final class ChatControllerContextReferenceContentSource: ContextReferenceContent } } - -extension Peer { - func canSetupAutoremoveTimeout(accountPeerId: PeerId) -> Bool { - if let _ = self as? TelegramSecretChat { - return false - } else if let group = self as? TelegramGroup { - if case .creator = group.role { - return true - } else if case let .admin(rights, _) = group.role { - if rights.rights.contains(.canDeleteMessages) { - return true - } - } - } else if let user = self as? TelegramUser { - if user.id != accountPeerId && user.botInfo == nil { - return true - } - } else if let channel = self as? TelegramChannel { - if channel.hasPermission(.deleteAllMessages) { - return true - } - } - - return false - } -} - -func canAddMessageReactions(message: Message) -> Bool { - if message.id.namespace != Namespaces.Message.Cloud { - return false - } - if let peer = message.peers[message.id.peerId] { - if let _ = peer as? TelegramSecretChat { - return false - } - } else { - return false - } - for media in message.media { - if let _ = media as? TelegramMediaAction { - return false - } else if let story = media as? TelegramMediaStory { - if story.isMention { - return false - } - } else if let _ = media as? TelegramMediaExpiredContent { - return false - } - } - return true -} - enum AllowedReactions { case set(Set) case all diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 5d73674c55..98862f8173 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -32,6 +32,12 @@ import ChatInputPanelNode import ChatInputContextPanelNode import TextSelectionNode import ReplyAccessoryPanelNode +import ChatMessageItemView +import ChatMessageSelectionNode +import ManagedDiceAnimationNode +import ChatMessageTransitionNode +import ChatLoadingNode +import ChatRecentActionsController final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -265,7 +271,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var dropDimNode: ASDisplayNode? - let messageTransitionNode: ChatMessageTransitionNode + let messageTransitionNode: ChatMessageTransitionNodeImpl private let presentationContextMarker = ASDisplayNode() @@ -394,27 +400,28 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputContextOverTextPanelContainer = ChatControllerTitlePanelNodeContainer() var source: ChatHistoryListSource - if case let .messageOptions(_, messageIds, info, options) = subject { - let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId), options) - |> map { messages, accountPeer, options -> ([Message], Int32, Bool) in - var messages = messages - let forwardedMessageIds = Set(messages.map { $0.id }) - messages.sort(by: { lhsMessage, rhsMessage in - return lhsMessage.id > rhsMessage.id - }) - messages = messages.map { message in - var flags = message.flags - flags.remove(.Incoming) - flags.remove(.IsIncomingMask) - - var hideNames = options.hideNames - if message.id.peerId == accountPeer.id && message.forwardInfo == nil { - hideNames = true - } - - var attributes = message.attributes - attributes = attributes.filter({ attribute in - if case .forward = info.kind { + if case let .messageOptions(_, messageIds, info) = subject { + switch info { + case let .forward(forward): + let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId), forward.options) + |> map { messages, accountPeer, options -> ([Message], Int32, Bool) in + var messages = messages + let forwardedMessageIds = Set(messages.map { $0.id }) + messages.sort(by: { lhsMessage, rhsMessage in + return lhsMessage.index > rhsMessage.index + }) + messages = messages.map { message in + var flags = message.flags + flags.remove(.Incoming) + flags.remove(.IsIncomingMask) + + var hideNames = options.hideNames + if message.id.peerId == accountPeer.id && message.forwardInfo == nil { + hideNames = true + } + + var attributes = message.attributes + attributes = attributes.filter({ attribute in if attribute is EditedMessageAttribute { return false } @@ -438,15 +445,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if attribute is ReactionsMessageAttribute { return false } - } - return true - }) - - var messageText = message.text - var messageMedia = message.media - var hasDice = false - - if case .forward = info.kind { + return true + }) + + var messageText = message.text + var messageMedia = message.media + var hasDice = false + if hideNames { for media in message.media { if options.hideCaptions { @@ -478,19 +483,117 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } return message.withUpdatedFlags(flags).withUpdatedText(messageText).withUpdatedMedia(messageMedia).withUpdatedTimestamp(Int32(context.account.network.context.globalTime())).withUpdatedAttributes(attributes).withUpdatedAuthor(accountPeer).withUpdatedForwardInfo(forwardInfo) - } else { + } + + return (messages, Int32(messages.count), false) + } + source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), loadMore: nil) + case .reply: + let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId)) + |> map { messages, accountPeer -> ([Message], Int32, Bool) in + var messages = messages + messages.sort(by: { lhsMessage, rhsMessage in + return lhsMessage.timestamp > rhsMessage.timestamp + }) + messages = messages.map { message in return message } + + return (messages, Int32(messages.count), false) } - - return (messages, Int32(messages.count), false) + source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), loadMore: nil) + case let .link(link): + let messages = link.options + |> mapToSignal { options -> Signal<(ChatControllerSubject.LinkOptions, Peer, Message?), NoError> in + if let replyMessageId = options.replyMessageId { + return combineLatest( + context.account.postbox.messagesAtIds([replyMessageId]), + context.account.postbox.loadedPeerWithId(context.account.peerId) + ) + |> map { messages, peer -> (ChatControllerSubject.LinkOptions, Peer, Message?) in + return (options, peer, messages.first) + } + } else { + return context.account.postbox.loadedPeerWithId(context.account.peerId) + |> map { peer -> (ChatControllerSubject.LinkOptions, Peer, Message?) in + return (options, peer, nil) + } + } + } + |> map { options, accountPeer, replyMessage -> ([Message], Int32, Bool) in + var peers = SimpleDictionary() + peers[accountPeer.id] = accountPeer + + var associatedMessages = SimpleDictionary() + + var media: [Media] = [] + if case let .Loaded(content) = options.webpage.content { + var displayOptions: TelegramMediaWebpageDisplayOptions = .default + + if options.linkBelowText { + displayOptions.position = .belowText + } else { + displayOptions.position = .aboveText + } + + if options.largeMedia { + displayOptions.largeMedia = true + } else { + displayOptions.largeMedia = false + } + + media.append(TelegramMediaWebpage(webpageId: options.webpage.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions)))) + } + + var attributes: [MessageAttribute] = [] + attributes.append(TextEntitiesMessageAttribute(entities: options.messageEntities)) + + if let replyMessage { + associatedMessages[replyMessage.id] = replyMessage + + var mappedQuote: EngineMessageReplyQuote? + if let quote = options.replyQuote { + mappedQuote = EngineMessageReplyQuote(text: quote, entities: []) + } + + attributes.append(ReplyMessageAttribute(messageId: replyMessage.id, threadMessageId: nil, quote: mappedQuote)) + } + + let message = Message( + stableId: 1, + stableVersion: 1, + id: MessageId(peerId: accountPeer.id, namespace: 0, id: 1), + globallyUniqueId: nil, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: Int32(Date().timeIntervalSince1970), + flags: [], + tags: [], + globalTags: [], + localTags: [], + forwardInfo: nil, + author: accountPeer, + text: options.messageText, + attributes: attributes, + media: media, + peers: peers, + associatedMessages: associatedMessages, + associatedMessageIds: [], + associatedMedia: [:], + associatedThreadInfo: nil, + associatedStories: [:] + ) + + return ([message], 1, false) + } + source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), loadMore: nil) } - source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), loadMore: nil) } else { source = .default } - var getMessageTransitionNode: (() -> ChatMessageTransitionNode?)? + var getMessageTransitionNode: (() -> ChatMessageTransitionNodeImpl?)? self.historyNode = ChatHistoryListNode(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), messageTransitionNode: { return getMessageTransitionNode?() }) @@ -506,7 +609,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var getContentAreaInScreenSpaceImpl: (() -> CGRect)? var onTransitionEventImpl: ((ContainedViewLayoutTransition) -> Void)? - self.messageTransitionNode = ChatMessageTransitionNode(listNode: self.historyNode, getContentAreaInScreenSpace: { + self.messageTransitionNode = ChatMessageTransitionNodeImpl(listNode: self.historyNode, getContentAreaInScreenSpace: { return getContentAreaInScreenSpaceImpl?() ?? CGRect() }, onTransitionEvent: { transition in onTransitionEventImpl?(transition) @@ -2981,12 +3084,31 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { switch self.chatPresentationInterfaceState.mode { case .standard(previewing: true): - if let subject = self.controller?.subject, case let .messageOptions(_, _, info, _) = subject, case .reply = info.kind { + if let subject = self.controller?.subject, case let .messageOptions(_, _, info) = subject, case .reply = info { + if let controller = self.controller { + if let result = controller.presentationContext.hitTest(view: self.view, point: point, with: event) { + return result + } + } + if let result = self.historyNode.view.hitTest(self.view.convert(point, to: self.historyNode.view), with: event), let node = result.asyncdisplaykit_node { if node is TextSelectionNode { return result } } + } else if let subject = self.controller?.subject, case let .messageOptions(_, _, info) = subject, case .link = info { + if let controller = self.controller { + if let result = controller.presentationContext.hitTest(view: self.view, point: point, with: event) { + return result + } + } + + if let result = self.historyNode.view.hitTest(self.view.convert(point, to: self.historyNode.view), with: event), let node = result.asyncdisplaykit_node { + if let textNode = node as? TextAccessibilityOverlayNode { + let _ = textNode + return result + } + } } if let result = self.historyNode.view.hitTest(self.view.convert(point, to: self.historyNode.view), with: event), let node = result.asyncdisplaykit_node, node is ChatMessageSelectionNode || node is GridMessageSelectionNode { @@ -3354,7 +3476,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if self.shouldAnimateMessageTransition, let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode, let textInput = inputPanelNode.makeSnapshotForTransition() { usedCorrelationId = correlationId - let source: ChatMessageTransitionNode.Source = .textInput(textInput: textInput, replyPanel: replyPanel) + let source: ChatMessageTransitionNodeImpl.Source = .textInput(textInput: textInput, replyPanel: replyPanel) self.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: { }) } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 6c7c7ffe6f..f511b9dafa 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -14,6 +14,7 @@ import ChatPresentationInterfaceState import WallpaperBackgroundNode import ComponentFlow import EmojiStatusComponent +import ChatLoadingNode private protocol ChatEmptyNodeContent { func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index fbb3b17d8a..2078f00d80 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -6,6 +6,7 @@ import Emoji import AccountContext import TelegramPresentationData import ChatHistoryEntry +import ChatMessageItemCommon func chatHistoryEntriesForView( location: ChatLocation, diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index fb3c3767d9..9e01071052 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -26,12 +26,10 @@ import TranslateUI import ChatHistoryEntry import ChatOverscrollControl import ChatBotInfoItem - -extension ChatReplyThreadMessage { - var effectiveTopId: MessageId { - return self.channelMessageId ?? self.messageId - } -} +import ChatMessageItem +import ChatMessageItemImpl +import ChatMessageItemView +import ChatMessageTransitionNode struct ChatTopVisibleMessageRange: Equatable { var lowerBound: MessageIndex @@ -226,7 +224,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca let item: ListViewItem 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, location: location)) + item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -244,7 +242,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca let item: ListViewItem switch mode { case .bubbles: - item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) + item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) case .list: assertionFailure() item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) @@ -271,7 +269,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca let item: ListViewItem 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, location: location)) + item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -289,7 +287,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca let item: ListViewItem switch mode { case .bubbles: - item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) + item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) case .list: assertionFailure() item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) @@ -666,7 +664,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var toLang: String? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNode? = { nil }) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl? = { nil }) { var tagMask = tagMask if case .pinnedMessages = subject { tagMask = .pinned @@ -1019,7 +1017,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private func beginChatHistoryTransitions( selectedMessages: Signal?, NoError>, - messageTransitionNode: @escaping () -> ChatMessageTransitionNode? + messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl? ) { let context = self.context let chatLocation = self.chatLocation @@ -1505,9 +1503,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let previousSelectedMessages = previousValueAndVersion?.2 if let previousVersion = previousValueAndVersion?.1 { - if !GlobalExperimentalSettings.isAppStoreBuild { - precondition(update.1 >= previousVersion) - } assert(update.1 >= previousVersion) } @@ -1768,7 +1763,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf.forEachItemHeaderNode { itemHeaderNode in if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode { dateNode.updatePresentationData(chatPresentationData, context: strongSelf.context) - } else if let avatarNode = itemHeaderNode as? ChatMessageAvatarHeaderNode { + } else if let avatarNode = itemHeaderNode as? ChatMessageAvatarHeaderNodeImpl { avatarNode.updatePresentationData(chatPresentationData, context: strongSelf.context) } else if let dateNode = itemHeaderNode as? ListMessageDateHeaderNode { dateNode.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) @@ -3559,7 +3554,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let item: ListViewItem 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, location: location)) + item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -3615,7 +3610,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let item: ListViewItem 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, location: location)) + item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index fd150168c3..e1beffa6d4 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -12,6 +12,7 @@ import SearchUI import TelegramUIPreferences import ListMessageItem import ChatControllerInteraction +import ChatMessageItemView private enum ChatHistorySearchEntryStableId: Hashable { case messageId(MessageId) @@ -352,8 +353,6 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { itemNode.updateHiddenMedia() } else if let itemNode = itemNode as? ListMessageNode { itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateHiddenMedia() } } } @@ -369,10 +368,6 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { transitionNode = result } - } else if let itemNode = itemNode as? GridMessageItemNode { - if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { - transitionNode = result - } } } return transitionNode diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index 63b3f78d9f..51e5a56758 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -10,6 +10,9 @@ import ForwardAccessoryPanelNode import ReplyAccessoryPanelNode func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AccessoryPanelNode?, chatControllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? { + if case .standard(previewing: true) = chatPresentationInterfaceState.mode { + return nil + } if let _ = chatPresentationInterfaceState.interfaceState.selectionState { return nil } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index f31be0cb27..6a4f1d8db8 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -33,6 +33,9 @@ import SettingsUI import PremiumUI import TextNodeWithEntities import ChatControllerInteraction +import ChatMessageItemCommon +import ChatMessageItemView +import ChatMessageBubbleItemNode private struct MessageContextMenuData { let starStatus: Bool? diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 6223fc77fb..ea75b73364 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -6,26 +6,7 @@ import TelegramCore import TelegramPresentationData import AccountContext import ChatPresentationInterfaceState - -enum ChatNavigationButtonAction: Equatable { - case openChatInfo(expandAvatar: Bool) - case clearHistory - case clearCache - case cancelMessageSelection - case search - case dismiss - case toggleInfoPanel - case spacer -} - -struct ChatNavigationButton: Equatable { - let action: ChatNavigationButtonAction - let buttonItem: UIBarButtonItem - - static func ==(lhs: ChatNavigationButton, rhs: ChatNavigationButton) -> Bool { - return lhs.action == rhs.action && lhs.buttonItem === rhs.buttonItem - } -} +import ChatNavigationButton func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, subject: ChatControllerSubject?, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? { if let _ = presentationInterfaceState.interfaceState.selectionState { diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index cb99cd36d5..67be725233 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -5,6 +5,7 @@ import ContextUI import Postbox import TelegramCore import SwiftSignalKit +import ChatMessageItemView final class ChatMessageContextLocationContentSource: ContextLocationContentSource { private let controller: ViewController @@ -20,7 +21,6 @@ final class ChatMessageContextLocationContentSource: ContextLocationContentSourc } } - final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool = false let ignoreContentTouches: Bool = false diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 02fc77f16f..d2ebffd67e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -13,6 +13,12 @@ import ChatControllerInteraction import FeaturedStickersScreen import ChatTextInputMediaRecordingButton import ReplyAccessoryPanelNode +import ChatMessageItemView +import ChatMessageStickerItemNode +import ChatMessageInstantVideoItemNode +import ChatMessageAnimatedStickerItemNode +import ChatMessageTransitionNode +import ChatMessageBubbleItemNode private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect { if let presentationLayer = fromView.layer.presentation() { @@ -97,7 +103,7 @@ private final class OverlayTransitionContainerController: ViewController, Standa } } -public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransitionProtocol { +public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTransitionNode, ChatMessageTransitionProtocol { static let animationDuration: Double = 0.3 static let verticalAnimationControlPoints: (Float, Float, Float, Float) = (0.19919472913616398, 0.010644531250000006, 0.27920937042459737, 0.91025390625) @@ -232,7 +238,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti case groupedMediaInput(GroupedMediaInput) } - final class DecorationItemNode: ASDisplayNode { + final class DecorationItemNodeImpl: ASDisplayNode, ChatMessageTransitionNode.DecorationItemNode { let itemNode: ChatMessageItemView let contentView: UIView private let getContentAreaInScreenSpace: () -> CGRect @@ -285,7 +291,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti private final class AnimatingItemNode: ASDisplayNode { let itemNode: ChatMessageItemView private let contextSourceNode: ContextExtractedContentContainingNode - private let source: ChatMessageTransitionNode.Source + private let source: ChatMessageTransitionNodeImpl.Source private let getContentAreaInScreenSpace: () -> CGRect private let scrollingContainer: ASDisplayNode @@ -297,7 +303,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti var animationEnded: (() -> Void)? var updateAfterCompletion: Bool = false - init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNode.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) { + init(itemNode: ChatMessageItemView, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) { self.itemNode = itemNode self.getContentAreaInScreenSpace = getContentAreaInScreenSpace @@ -325,7 +331,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti } func beginAnimation() { - let verticalDuration: Double = ChatMessageTransitionNode.animationDuration + let verticalDuration: Double = ChatMessageTransitionNodeImpl.animationDuration let horizontalDuration: Double = verticalDuration let delay: Double = 0.0 @@ -364,7 +370,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti let sourceBackgroundAbsoluteRect = initialTextInput.backgroundView.frame.offsetBy(dx: sourceRect.minX, dy: sourceRect.minY) let sourceAbsoluteRect = CGRect(origin: CGPoint(x: sourceBackgroundAbsoluteRect.minX, y: sourceBackgroundAbsoluteRect.maxY - self.contextSourceNode.contentRect.height), size: self.contextSourceNode.contentRect.size) - let textInput = ChatMessageTransitionNode.Source.TextInput(backgroundView: initialTextInput.backgroundView, contentView: initialTextInput.contentView, sourceRect: sourceRect, scrollOffset: initialTextInput.scrollOffset) + let textInput = ChatMessageTransitionNodeImpl.Source.TextInput(backgroundView: initialTextInput.backgroundView, contentView: initialTextInput.contentView, sourceRect: sourceRect, scrollOffset: initialTextInput.scrollOffset) textInput.backgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: sourceAbsoluteRect.height - sourceBackgroundAbsoluteRect.height), size: textInput.backgroundView.bounds.size) textInput.contentView.frame = textInput.contentView.frame.offsetBy(dx: 0.0, dy: sourceAbsoluteRect.height - sourceBackgroundAbsoluteRect.height) @@ -387,9 +393,9 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.itemNode.cancelInsertionAnimations() - let horizontalCurve = ChatMessageTransitionNode.horizontalAnimationCurve + let horizontalCurve = ChatMessageTransitionNodeImpl.horizontalAnimationCurve let horizontalTransition: ContainedViewLayoutTransition = .animated(duration: horizontalDuration, curve: horizontalCurve) - let verticalCurve = ChatMessageTransitionNode.verticalAnimationCurve + let verticalCurve = ChatMessageTransitionNodeImpl.verticalAnimationCurve let verticalTransition: ContainedViewLayoutTransition = .animated(duration: verticalDuration, curve: verticalCurve) let combinedTransition = CombinedTransition(horizontal: horizontalTransition, vertical: verticalTransition) @@ -407,19 +413,73 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), verticalCurve, verticalDuration) if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { - itemNode.animateContentFromTextInputField(textInput: textInput, transition: combinedTransition) + itemNode.animateContentFromTextInputField( + textInput: ChatMessageBubbleItemNode.AnimationTransitionTextInput( + backgroundView: textInput.backgroundView, + contentView: textInput.contentView, + sourceRect: textInput.sourceRect, + scrollOffset: textInput.scrollOffset + ), + transition: combinedTransition + ) if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageBubbleItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } else if let itemNode = self.itemNode as? ChatMessageAnimatedStickerItemNode { - itemNode.animateContentFromTextInputField(textInput: textInput, transition: combinedTransition) + itemNode.animateContentFromTextInputField( + textInput: ChatMessageAnimatedStickerItemNode.AnimationTransitionTextInput( + backgroundView: textInput.backgroundView, + contentView: textInput.contentView, + sourceRect: textInput.sourceRect, + scrollOffset: textInput.scrollOffset + ), + transition: combinedTransition + ) if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageAnimatedStickerItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } else if let itemNode = self.itemNode as? ChatMessageStickerItemNode { - itemNode.animateContentFromTextInputField(textInput: textInput, transition: combinedTransition) + itemNode.animateContentFromTextInputField( + textInput: ChatMessageStickerItemNode.AnimationTransitionTextInput( + backgroundView: textInput.backgroundView, + contentView: textInput.contentView, + sourceRect: textInput.sourceRect, + scrollOffset: textInput.scrollOffset + ), + transition: combinedTransition + ) if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageStickerItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } case let .stickerMediaInput(stickerMediaInput, replyPanel): @@ -458,34 +518,72 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti sourceReplyPanel = ReplyPanel(titleNode: replyPanel.titleNode, textNode: replyPanel.textNode, lineNode: replyPanel.lineNode, imageNode: replyPanel.imageNode, relativeSourceRect: replySourceAbsoluteFrame, relativeTargetRect: replySourceAbsoluteFrame.offsetBy(dx: 0.0, dy: replySourceAbsoluteFrame.height)) } - let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNodeImpl.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) if let itemNode = self.itemNode as? ChatMessageAnimatedStickerItemNode { - itemNode.animateContentFromStickerGridItem(stickerSource: stickerSource, transition: combinedTransition) + itemNode.animateContentFromStickerGridItem( + stickerSource: ChatMessageAnimatedStickerItemNode.AnimationTransitionSticker( + imageNode: stickerSource.imageNode, + animationNode: stickerSource.animationNode, + placeholderNode: stickerSource.placeholderNode, + imageLayer: stickerSource.imageLayer, + relativeSourceRect: stickerSource.relativeSourceRect + ), + transition: combinedTransition + ) if let sourceAnimationNode = stickerSource.animationNode { itemNode.animationNode?.setFrameIndex(sourceAnimationNode.currentFrameIndex) } if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageAnimatedStickerItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } else if let itemNode = self.itemNode as? ChatMessageStickerItemNode { - itemNode.animateContentFromStickerGridItem(stickerSource: stickerSource, transition: combinedTransition) + itemNode.animateContentFromStickerGridItem( + stickerSource: ChatMessageStickerItemNode.AnimationTransitionSticker( + imageNode: stickerSource.imageNode, + animationNode: stickerSource.animationNode, + placeholderNode: stickerSource.placeholderNode, + imageLayer: stickerSource.imageLayer, + relativeSourceRect: stickerSource.relativeSourceRect + ), + transition: combinedTransition + ) if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageStickerItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in guard let strongSelf = self else { return } strongSelf.endAnimation() }) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), ChatMessageTransitionNode.horizontalAnimationCurve, horizontalDuration) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), ChatMessageTransitionNode.verticalAnimationCurve, verticalDuration) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.horizontalAnimationCurve.mediaTimingFunction, additive: true) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), ChatMessageTransitionNodeImpl.horizontalAnimationCurve, horizontalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), ChatMessageTransitionNodeImpl.verticalAnimationCurve, verticalDuration) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.horizontalAnimationCurve.mediaTimingFunction, additive: true) switch stickerMediaInput { case .inputPanel, .universal: @@ -504,7 +602,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti container.isHidden = true - let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNodeImpl.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { if let contextContainer = itemNode.animateFromMicInput(micInputNode: snapshotView, transition: combinedTransition) { @@ -514,7 +612,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -contextContainer.contentRect.minX, dy: -contextContainer.contentRect.minY) contextContainer.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self, weak contextContainer, weak container] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self, weak contextContainer, weak container] _ in guard let strongSelf = self else { return } @@ -529,13 +627,13 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti strongSelf.endAnimation() }) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.horizontalAnimationCurve.mediaTimingFunction, additive: true) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.horizontalAnimationCurve.mediaTimingFunction, additive: true) } } } } case let .videoMessage(videoMessage): - let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNodeImpl.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) if let itemNode = self.itemNode as? ChatMessageInstantVideoItemNode { itemNode.cancelInsertionAnimations() @@ -551,9 +649,9 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti videoMessage.view.frame = videoMessage.view.frame.offsetBy(dx: targetAbsoluteRect.midX - sourceAbsoluteRect.midX, dy: targetAbsoluteRect.midY - sourceAbsoluteRect.midY) self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true) + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.verticalAnimationCurve.mediaTimingFunction, additive: true, completion: { [weak self] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.verticalAnimationCurve.mediaTimingFunction, additive: true, completion: { [weak self] _ in guard let strongSelf = self else { return } @@ -578,7 +676,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti let sourceBackgroundAbsoluteRect = snapshotView.frame let sourceAbsoluteRect = CGRect(origin: CGPoint(x: sourceBackgroundAbsoluteRect.midX - self.contextSourceNode.contentRect.size.width / 2.0, y: sourceBackgroundAbsoluteRect.midY - self.contextSourceNode.contentRect.size.height / 2.0), size: self.contextSourceNode.contentRect.size) - let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNodeImpl.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { itemNode.animateContentFromMediaInput(snapshotView: snapshotView, transition: combinedTransition) @@ -591,8 +689,8 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in guard let strongSelf = self else { return } @@ -607,8 +705,8 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti snapshotView?.removeFromSuperview() }) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), ChatMessageTransitionNode.horizontalAnimationCurve, horizontalDuration) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), ChatMessageTransitionNode.verticalAnimationCurve, verticalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), ChatMessageTransitionNodeImpl.horizontalAnimationCurve, horizontalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), ChatMessageTransitionNodeImpl.verticalAnimationCurve, verticalDuration) } } } else { @@ -629,7 +727,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.containerNode.addSubnode(self.contextSourceNode.contentNode) - let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNodeImpl.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) var targetContentRects: [CGRect] = [] if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { @@ -669,8 +767,8 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) - self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true) - self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true) + self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNodeImpl.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in guard let strongSelf = self else { return } @@ -696,8 +794,8 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti index += 1 } - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), ChatMessageTransitionNode.horizontalAnimationCurve, horizontalDuration) - self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), ChatMessageTransitionNode.verticalAnimationCurve, verticalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), ChatMessageTransitionNodeImpl.horizontalAnimationCurve, horizontalDuration) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), ChatMessageTransitionNodeImpl.verticalAnimationCurve, verticalDuration) } } } @@ -798,7 +896,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti private var currentPendingItems: [Int64: (Source, () -> Void)] = [:] private var animatingItemNodes: [AnimatingItemNode] = [] - private var decorationItemNodes: [DecorationItemNode] = [] + private var decorationItemNodes: [DecorationItemNodeImpl] = [] private var messageReactionContexts: [MessageReactionContext] = [] var hasScheduledTransitions: Bool { @@ -851,8 +949,8 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti self.listNode.setCurrentSendAnimationCorrelationIds(correlationIds) } - func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode { - let decorationItemNode = DecorationItemNode(itemNode: itemNode, contentView: decorationView, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace) + public func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode { + let decorationItemNode = DecorationItemNodeImpl(itemNode: itemNode, contentView: decorationView, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace) decorationItemNode.updateLayout(size: self.bounds.size) self.decorationItemNodes.append(decorationItemNode) @@ -867,10 +965,12 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti return decorationItemNode } - func remove(decorationNode: DecorationItemNode) { + public func remove(decorationNode: DecorationItemNode) { self.decorationItemNodes.removeAll(where: { $0 === decorationNode }) decorationNode.removeFromSupernode() - decorationNode.overlayController?.dismiss() + if let decorationNode = decorationNode as? DecorationItemNodeImpl { + decorationNode.overlayController?.dismiss() + } } private func beginAnimation(itemNode: ChatMessageItemView, source: Source) { @@ -921,7 +1021,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti animatingItemNode.frame = self.bounds animatingItemNode.beginAnimation() - self.onTransitionEvent(.animated(duration: ChatMessageTransitionNode.animationDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + self.onTransitionEvent(.animated(duration: ChatMessageTransitionNodeImpl.animationDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) } } diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 0a4c45610e..2cc3fddd5b 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -4219,7 +4219,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return nil } - func makeSnapshotForTransition() -> ChatMessageTransitionNode.Source.TextInput? { + func makeSnapshotForTransition() -> ChatMessageTransitionNodeImpl.Source.TextInput? { guard let backgroundImage = self.transparentTextInputBackgroundImage else { return nil } @@ -4242,7 +4242,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch contentView.frame = textInputNode.frame - return ChatMessageTransitionNode.Source.TextInput( + return ChatMessageTransitionNodeImpl.Source.TextInput( backgroundView: backgroundView, contentView: contentView, sourceRect: self.view.convert(self.bounds, to: nil), diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift.orig b/submodules/TelegramUI/Sources/ChatThemeScreen.swift.orig deleted file mode 100644 index e3816ed48b..0000000000 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift.orig +++ /dev/null @@ -1,1238 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SwiftSignalKit -import AccountContext -import SolidRoundedButtonNode -import TelegramPresentationData -import TelegramUIPreferences -import TelegramNotices -import PresentationDataUtils -import AnimationUI -import MergeLists -import MediaResources -import StickerResources -import WallpaperResources -import TooltipUI -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import ShimmerEffect - -private func closeButtonImage(theme: PresentationTheme) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(UIColor(rgb: 0x808084, alpha: 0.1).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setStrokeColor(theme.actionSheet.inputClearButtonColor.cgColor) - - context.move(to: CGPoint(x: 10.0, y: 10.0)) - context.addLine(to: CGPoint(x: 20.0, y: 20.0)) - context.strokePath() - - context.move(to: CGPoint(x: 20.0, y: 10.0)) - context.addLine(to: CGPoint(x: 10.0, y: 20.0)) - context.strokePath() - }) -} - -private struct ThemeSettingsThemeEntry: Comparable, Identifiable { - let index: Int - let emoticon: String? - let emojiFile: TelegramMediaFile? - let themeReference: PresentationThemeReference? - var selected: Bool - let theme: PresentationTheme - let strings: PresentationStrings - let wallpaper: TelegramWallpaper? - - var stableId: Int { - return index - } - - static func ==(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool { - if lhs.index != rhs.index { - return false - } - if lhs.emoticon != rhs.emoticon { - return false - } - - if lhs.themeReference?.index != rhs.themeReference?.index { - return false - } - if lhs.selected != rhs.selected { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.wallpaper != rhs.wallpaper { - return false - } - return true - } - - static func <(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem { - return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action) - } -} - - -private class ThemeSettingsThemeIconItem: ListViewItem { - let context: AccountContext - let emoticon: String? - let emojiFile: TelegramMediaFile? - let themeReference: PresentationThemeReference? - let selected: Bool - let theme: PresentationTheme - let strings: PresentationStrings - let wallpaper: TelegramWallpaper? - let action: (String?) -> Void - - public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) { - self.context = context - self.emoticon = emoticon - self.emojiFile = emojiFile - self.themeReference = themeReference - self.selected = selected - self.theme = theme - self.strings = strings - self.wallpaper = wallpaper - self.action = action - } - - public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ThemeSettingsThemeItemIconNode() - let (nodeLayout, apply) = node.asyncLayout()(self, params) - node.insets = nodeLayout.insets - node.contentSize = nodeLayout.contentSize - - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in - apply(false) - }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - assert(node() is ThemeSettingsThemeItemIconNode) - if let nodeValue = node() as? ThemeSettingsThemeItemIconNode { - let layout = nodeValue.asyncLayout() - async { - let (nodeLayout, apply) = layout(self, params) - Queue.mainQueue().async { - completion(nodeLayout, { _ in - apply(animation.isAnimated) - }) - } - } - } - } - } - - public var selectable = true - public func selected(listView: ListView) { - self.action(self.emoticon) - } -} - -private struct ThemeSettingsThemeItemNodeTransition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] - let crossfade: Bool - let entries: [ThemeSettingsThemeEntry] -} - -private func ensureThemeVisible(listNode: ListView, emoticon: String?, animated: Bool) -> Bool { - var resultNode: ThemeSettingsThemeItemIconNode? -<<<<<<< HEAD - var previousNode: ThemeSettingsThemeItemIconNode? - let _ = previousNode -======= -// var previousNode: ThemeSettingsThemeItemIconNode? ->>>>>>> beta - var nextNode: ThemeSettingsThemeItemIconNode? - listNode.forEachItemNode { node in - guard let node = node as? ThemeSettingsThemeItemIconNode else { - return - } - if resultNode == nil { - if node.item?.emoticon == emoticon { - resultNode = node - } else { -// previousNode = node - } - } else if nextNode == nil { - nextNode = node - } - } - if let resultNode = resultNode { - listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 57.0) - return true - } else { - return false - } -} - -private func preparedTransition(context: AccountContext, action: @escaping (String?) -> Void, from fromEntries: [ThemeSettingsThemeEntry], to toEntries: [ThemeSettingsThemeEntry], crossfade: Bool) -> ThemeSettingsThemeItemNodeTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action), directionHint: .Down) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, action: action), directionHint: nil) } - - return ThemeSettingsThemeItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, crossfade: crossfade, entries: toEntries) -} - -private var cachedBorderImages: [String: UIImage] = [:] -private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? { - let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)" - if let image = cachedBorderImages[key] { - return image - } else { - let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in - let bounds = CGRect(origin: CGPoint(), size: size) - context.clear(bounds) - - let lineWidth: CGFloat - if selected { - lineWidth = 2.0 - context.setLineWidth(lineWidth) - context.setStrokeColor(theme.list.itemBlocksBackgroundColor.cgColor) - - context.strokeEllipse(in: bounds.insetBy(dx: 3.0 + lineWidth / 2.0, dy: 3.0 + lineWidth / 2.0)) - - var accentColor = theme.list.itemAccentColor - if accentColor.rgb == 0xffffff { - accentColor = UIColor(rgb: 0x999999) - } - context.setStrokeColor(accentColor.cgColor) - } else { - context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor) - lineWidth = 1.0 - } - - if bordered || selected { - context.setLineWidth(lineWidth) - context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) - } - })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) - cachedBorderImages[key] = image - return image - } -} - -private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { - private let containerNode: ASDisplayNode - private let emojiContainerNode: ASDisplayNode - private let imageNode: TransformImageNode - private let overlayNode: ASImageNode - private let textNode: TextNode - private let emojiNode: TextNode - private let emojiImageNode: TransformImageNode - private var animatedStickerNode: AnimatedStickerNode? - private var placeholderNode: StickerShimmerEffectNode - var snapshotView: UIView? - - var item: ThemeSettingsThemeIconItem? - - override var visibility: ListViewItemNodeVisibility { - didSet { - self.visibilityStatus = self.visibility != .none - } - } - - private var visibilityStatus: Bool = false { - didSet { - if self.visibilityStatus != oldValue { - self.animatedStickerNode?.visibility = self.visibilityStatus - } - } - } - - private let stickerFetchedDisposable = MetaDisposable() - - init() { - self.containerNode = ASDisplayNode() - self.emojiContainerNode = ASDisplayNode() - - self.imageNode = TransformImageNode() - self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 82.0, height: 108.0)) - self.imageNode.isLayerBacked = true - self.imageNode.cornerRadius = 8.0 - self.imageNode.clipsToBounds = true - - self.overlayNode = ASImageNode() - self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 84.0, height: 110.0)) - self.overlayNode.isLayerBacked = true - - self.textNode = TextNode() - self.textNode.isUserInteractionEnabled = false - self.textNode.displaysAsynchronously = false - - self.emojiNode = TextNode() - self.emojiNode.isUserInteractionEnabled = false - self.emojiNode.displaysAsynchronously = false - - self.emojiImageNode = TransformImageNode() - - self.placeholderNode = StickerShimmerEffectNode() - - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.imageNode) - self.containerNode.addSubnode(self.overlayNode) - self.containerNode.addSubnode(self.textNode) - - self.addSubnode(self.emojiContainerNode) - self.emojiContainerNode.addSubnode(self.emojiNode) - self.emojiContainerNode.addSubnode(self.emojiImageNode) - self.emojiContainerNode.addSubnode(self.placeholderNode) - - var firstTime = true - self.emojiImageNode.imageUpdated = { [weak self] image in - guard let strongSelf = self else { - return - } - if image != nil { - strongSelf.removePlaceholder(animated: !firstTime) - if firstTime { - strongSelf.emojiImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - firstTime = false - } - } - - deinit { - self.stickerFetchedDisposable.dispose() - } - - private func removePlaceholder(animated: Bool) { - if !animated { - self.placeholderNode.removeFromSupernode() - } else { - self.placeholderNode.alpha = 0.0 - self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in - self?.placeholderNode.removeFromSupernode() - }) - } - } - - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { - let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) - self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize) - } - - override func selected() { - let wasSelected = self.item?.selected ?? false - super.selected() - - if let animatedStickerNode = self.animatedStickerNode { - Queue.mainQueue().after(0.1) { - if !wasSelected { - animatedStickerNode.seekTo(.frameIndex(0)) - animatedStickerNode.play() - - let scale: CGFloat = 2.6 - animatedStickerNode.transform = CATransform3DMakeScale(scale, scale, 1.0) - animatedStickerNode.layer.animateSpring(from: 1.0 as NSNumber, to: scale as NSNumber, keyPath: "transform.scale", duration: 0.45) - - animatedStickerNode.completed = { [weak animatedStickerNode, weak self] _ in - guard let item = self?.item, item.selected else { - return - } - animatedStickerNode?.transform = CATransform3DIdentity - animatedStickerNode?.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45) - } - } - } - } - - } - - func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { - let makeTextLayout = TextNode.asyncLayout(self.textNode) - let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode) - let makeImageLayout = self.imageNode.asyncLayout() - - let currentItem = self.item - - return { [weak self] item, params in - var updatedEmoticon = false - var updatedThemeReference = false - var updatedTheme = false - var updatedWallpaper = false - var updatedSelected = false - - if currentItem?.emoticon != item.emoticon { - updatedEmoticon = true - } - if currentItem?.themeReference != item.themeReference { - updatedThemeReference = true - } - if currentItem?.wallpaper != item.wallpaper { - updatedWallpaper = true - } - if currentItem?.theme !== item.theme { - updatedTheme = true - } - if currentItem?.selected != item.selected { - updatedSelected = true - } - - let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor) - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - - let emoticon = item.emoticon - let title = NSAttributedString(string: emoticon != nil ? "" : "❌", font: Font.regular(22.0), textColor: .black) - let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - - let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 120.0, height: 90.0), insets: UIEdgeInsets()) - return (itemLayout, { animated in - if let strongSelf = self { - strongSelf.item = item - - if updatedThemeReference || updatedWallpaper { - if let themeReference = item.themeReference { - strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, emoticon: true)) - strongSelf.imageNode.backgroundColor = nil - } - } - if item.themeReference == nil { - strongSelf.imageNode.backgroundColor = item.theme.actionSheet.opaqueItemBackgroundColor - } - - if updatedTheme || updatedSelected { - strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: false, selected: item.selected) - } - - if !item.selected && currentItem?.selected == true, let animatedStickerNode = strongSelf.animatedStickerNode { - animatedStickerNode.transform = CATransform3DIdentity - - let initialScale: CGFloat = CGFloat((animatedStickerNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0) - animatedStickerNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45) - } - - strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((90.0 - textLayout.size.width) / 2.0), y: 24.0), size: textLayout.size) - strongSelf.textNode.isHidden = item.emoticon != nil - - strongSelf.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0)) - - strongSelf.emojiContainerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - strongSelf.emojiContainerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0)) - - let _ = textApply() - let _ = emojiApply() - - let imageSize = CGSize(width: 82.0, height: 108.0) - strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 6.0), size: imageSize) - let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) - applyLayout() - - strongSelf.overlayNode.frame = strongSelf.imageNode.frame.insetBy(dx: -1.0, dy: -1.0) - strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 79.0), size: CGSize(width: 90.0, height: 30.0)) - - let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) - if let file = item.emojiFile, updatedEmoticon { - let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets())) - imageApply() - strongSelf.emojiImageNode.setSignal(chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: file.resource, animated: true, nilIfEmpty: true)) - strongSelf.emojiImageNode.frame = emojiFrame - - let animatedStickerNode: AnimatedStickerNode - if let current = strongSelf.animatedStickerNode { - animatedStickerNode = current - } else { - animatedStickerNode = AnimatedStickerNode() - animatedStickerNode.started = { [weak self] in - self?.emojiImageNode.isHidden = true - } - strongSelf.animatedStickerNode = animatedStickerNode - strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode) - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) - - animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) - } - animatedStickerNode.autoplay = true - animatedStickerNode.visibility = strongSelf.visibilityStatus - - strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) - - let thumbnailDimensions = PixelDimensions(width: 512, height: 512) - strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, imageSize: thumbnailDimensions.cgSize) - strongSelf.placeholderNode.frame = emojiFrame - } - - if let animatedStickerNode = strongSelf.animatedStickerNode { - animatedStickerNode.frame = emojiFrame - animatedStickerNode.updateLayout(size: emojiFrame.size) - } - } - }) - } - } - - func crossfade() { - if let snapshotView = self.containerNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.transform = self.containerNode.view.transform - snapshotView.frame = self.containerNode.view.frame - self.view.insertSubview(snapshotView, aboveSubview: self.containerNode.view) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { - super.animateInsertion(currentTimestamp, duration: duration, short: short) - - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - super.animateRemoved(currentTimestamp, duration: duration) - - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - super.animateAdded(currentTimestamp, duration: duration) - - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } -} - -final class ChatThemeScreen: ViewController { - static let themeCrossfadeDuration: Double = 0.3 - static let themeCrossfadeDelay: Double = 0.25 - - private var controllerNode: ChatThemeScreenNode { - return self.displayNode as! ChatThemeScreenNode - } - - private var animatedIn = false - - private let context: AccountContext - private let animatedEmojiStickers: [String: [StickerPackItem]] - private let initiallySelectedEmoticon: String? - private let peerName: String - private let previewTheme: (String?, Bool?) -> Void - private let completion: (String?) -> Void - - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - var dismissed: (() -> Void)? - - var passthroughHitTestImpl: ((CGPoint) -> UIView?)? { - didSet { - if self.isNodeLoaded { - self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl - } - } - } - - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) { - self.context = context - self.presentationData = updatedPresentationData.initial - self.animatedEmojiStickers = animatedEmojiStickers - self.initiallySelectedEmoticon = initiallySelectedEmoticon - self.peerName = peerName - self.previewTheme = previewTheme - self.completion = completion - - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore - - self.blocksBackgroundWhenInOverlay = true - - self.presentationDataDisposable = (updatedPresentationData.signal - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.presentationData = presentationData - strongSelf.controllerNode.updatePresentationData(presentationData) - } - }) - - self.statusBar.statusBarStyle = .Ignore - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDataDisposable?.dispose() - } - - override public func loadDisplayNode() { - self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedEmoticon: self.initiallySelectedEmoticon, peerName: self.peerName) - self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl - self.controllerNode.previewTheme = { [weak self] emoticon, dark in - guard let strongSelf = self else { - return - } - strongSelf.previewTheme((emoticon ?? ""), dark) - } - self.controllerNode.present = { [weak self] c in - self?.present(c, in: .current) - } - self.controllerNode.completion = { [weak self] emoticon in - guard let strongSelf = self else { - return - } - strongSelf.dismiss() - if strongSelf.initiallySelectedEmoticon == nil && emoticon == nil { - } else { - strongSelf.completion(emoticon) - } - } - self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - self.controllerNode.cancel = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.dismiss() - strongSelf.previewTheme(nil, nil) - } - } - - override public func loadView() { - super.loadView() - - self.view.disablesInteractiveTransitionGestureRecognizer = true - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.animatedIn { - self.animatedIn = true - self.controllerNode.animateIn() - } - } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.forEachController({ controller in - if let controller = controller as? TooltipScreen { - controller.dismiss() - } - return true - }) - - self.controllerNode.animateOut(completion: completion) - - self.dismissed?() - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - } - - func dimTapped() { - self.controllerNode.dimTapped() - } -} - -private func iconColors(theme: PresentationTheme) -> [String: UIColor] { - let accentColor = theme.actionSheet.controlAccentColor - var colors: [String: UIColor] = [:] - colors["Sunny.Path 14.Path.Stroke 1"] = accentColor - colors["Sunny.Path 15.Path.Stroke 1"] = accentColor - colors["Path.Path.Stroke 1"] = accentColor - colors["Sunny.Path 39.Path.Stroke 1"] = accentColor - colors["Sunny.Path 24.Path.Stroke 1"] = accentColor - colors["Sunny.Path 25.Path.Stroke 1"] = accentColor - colors["Sunny.Path 18.Path.Stroke 1"] = accentColor - colors["Sunny.Path 41.Path.Stroke 1"] = accentColor - colors["Sunny.Path 43.Path.Stroke 1"] = accentColor - colors["Path 10.Path.Fill 1"] = accentColor - colors["Path 11.Path.Fill 1"] = accentColor - return colors -} - -private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelegate { - private let context: AccountContext - private var presentationData: PresentationData - private weak var controller: ChatThemeScreen? - - private let dimNode: ASDisplayNode - private let wrappingScrollNode: ASScrollNode - private let contentContainerNode: ASDisplayNode - private let topContentContainerNode: SparseNode - private let effectNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let contentBackgroundNode: ASDisplayNode - private let titleNode: ASTextNode - private let textNode: ImmediateTextNode - private let cancelButton: HighlightableButtonNode - private let switchThemeButton: HighlightTrackingButtonNode - private let animationContainerNode: ASDisplayNode - private var animationNode: AnimationNode - private let doneButton: SolidRoundedButtonNode - - private let listNode: ListView - private var entries: [ThemeSettingsThemeEntry]? - private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = [] - private var initialized = false - - private let peerName: String - - private let initiallySelectedEmoticon: String? - private var selectedEmoticon: String? { - didSet { - self.selectedEmoticonPromise.set(self.selectedEmoticon) - } - } - private var selectedEmoticonPromise: ValuePromise - - private var isDarkAppearancePromise: ValuePromise - private var isDarkAppearance: Bool = false { - didSet { - self.isDarkAppearancePromise.set(self.isDarkAppearance) - } - } - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - private let disposable = MetaDisposable() - - var present: ((ViewController) -> Void)? - var previewTheme: ((String?, Bool?) -> Void)? - var completion: ((String?) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData, controller: ChatThemeScreen, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, peerName: String) { - self.context = context - self.controller = controller - self.initiallySelectedEmoticon = initiallySelectedEmoticon - self.peerName = peerName - self.selectedEmoticon = initiallySelectedEmoticon - self.selectedEmoticonPromise = ValuePromise(initiallySelectedEmoticon) - self.presentationData = presentationData - - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = .clear - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false - - self.topContentContainerNode = SparseNode() - self.topContentContainerNode.isOpaque = false - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.cornerRadius = 16.0 - - self.isDarkAppearance = self.presentationData.theme.overallDarkAppearance - self.isDarkAppearancePromise = ValuePromise(self.presentationData.theme.overallDarkAppearance) - - let backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - let textColor = self.presentationData.theme.actionSheet.primaryTextColor - let secondaryTextColor = self.presentationData.theme.actionSheet.secondaryTextColor - let blurStyle: UIBlurEffect.Style = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark - - self.effectNode = ASDisplayNode(viewBlock: { - return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) - }) - - self.contentBackgroundNode = ASDisplayNode() - self.contentBackgroundNode.backgroundColor = backgroundColor - - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(16.0), textColor: textColor) - - self.textNode = ImmediateTextNode() - self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(12.0), textColor: secondaryTextColor) - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) - - self.switchThemeButton = HighlightTrackingButtonNode() - self.animationContainerNode = ASDisplayNode() - self.animationContainerNode.isUserInteractionEnabled = false - - self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) - self.animationNode.isUserInteractionEnabled = false - - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) - self.doneButton.title = initiallySelectedEmoticon == nil ? self.presentationData.strings.Conversation_Theme_DontSetTheme : self.presentationData.strings.Conversation_Theme_Apply - - self.listNode = ListView() - self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - super.init() - - self.backgroundColor = nil - self.isOpaque = false - - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.backgroundNode) - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - self.wrappingScrollNode.addSubnode(self.topContentContainerNode) - - self.backgroundNode.addSubnode(self.effectNode) - self.backgroundNode.addSubnode(self.contentBackgroundNode) - self.contentContainerNode.addSubnode(self.titleNode) - self.contentContainerNode.addSubnode(self.textNode) - self.contentContainerNode.addSubnode(self.doneButton) - - self.topContentContainerNode.addSubnode(self.animationContainerNode) - self.animationContainerNode.addSubnode(self.animationNode) - self.topContentContainerNode.addSubnode(self.switchThemeButton) - self.topContentContainerNode.addSubnode(self.listNode) - self.topContentContainerNode.addSubnode(self.cancelButton) - - self.switchThemeButton.addTarget(self, action: #selector(self.switchThemePressed), forControlEvents: .touchUpInside) - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.doneButton.pressed = { [weak self] in - if let strongSelf = self { - strongSelf.doneButton.isUserInteractionEnabled = false - strongSelf.completion?(strongSelf.selectedEmoticon) - } - } - - self.disposable.set(combineLatest(queue: Queue.mainQueue(), self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] themes, selectedEmoticon, isDarkAppearance in - guard let strongSelf = self else { - return - } - - let isFirstTime = strongSelf.entries == nil - let presentationData = strongSelf.presentationData - - var entries: [ThemeSettingsThemeEntry] = [] - entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) - for theme in themes { - let emoticon = theme.emoji - entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil)) - } - - let action: (String?) -> Void = { [weak self] emoticon in - if let strongSelf = self, strongSelf.selectedEmoticon != emoticon { - strongSelf.animateCrossfade(animateIcon: true) - - strongSelf.previewTheme?(emoticon, strongSelf.isDarkAppearance) - strongSelf.selectedEmoticon = emoticon - let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true) - - let doneButtonTitle: String - if emoticon == nil { - doneButtonTitle = strongSelf.initiallySelectedEmoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_DontSetTheme : strongSelf.presentationData.strings.Conversation_Theme_Reset - } else { - doneButtonTitle = strongSelf.presentationData.strings.Conversation_Theme_Apply - } - strongSelf.doneButton.title = doneButtonTitle - - strongSelf.themeSelectionsCount += 1 - if strongSelf.themeSelectionsCount == 2 { - strongSelf.maybePresentPreviewTooltip() - } - } - } - let previousEntries = strongSelf.entries ?? [] - let crossfade = previousEntries.count != entries.count - let transition = preparedTransition(context: strongSelf.context, action: action, from: previousEntries, to: entries, crossfade: crossfade) - strongSelf.enqueueTransition(transition) - - strongSelf.entries = entries - - if isFirstTime { - for theme in themes { - if let wallpaper = theme.theme.settings?.wallpaper, case let .file(file) = wallpaper { - let account = strongSelf.context.account - let accountManager = strongSelf.context.sharedContext.accountManager - let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation()) - if !FileManager.default.fileExists(atPath: path) { - let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in - let accountResource = account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPreparedPatternWallpaperRepresentation(), complete: false, fetch: true) - - let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .media(media: .standalone(media: file.file), resource: file.file.resource)) - let fetchedFullSizeDisposable = fetchedFullSize.start() - let fullSizeDisposable = accountResource.start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) - - if next.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedRead) { - accountManager.mediaBox.storeCachedResourceRepresentation(file.file.resource, representation: CachedPreparedPatternWallpaperRepresentation(), data: data) - } - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedFullSizeDisposable.dispose() - fullSizeDisposable.dispose() - } - } - let _ = accountFullSizeData.start() - } - } - } - } - })) - - self.switchThemeButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.animationNode.layer.removeAnimation(forKey: "opacity") - strongSelf.animationNode.alpha = 0.4 - } else { - strongSelf.animationNode.alpha = 1.0 - strongSelf.animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - - private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) { - self.enqueuedTransitions.append(transition) - - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - - private func dequeueTransition() { - guard let transition = self.enqueuedTransitions.first else { - return - } - self.enqueuedTransitions.remove(at: 0) - - var options = ListViewDeleteAndInsertOptions() - if self.initialized && transition.crossfade { - options.insert(.AnimateCrossfade) - } - options.insert(.Synchronous) - - var scrollToItem: ListViewScrollToItem? - if !self.initialized { - if let index = transition.entries.firstIndex(where: { entry in - return entry.emoticon == self.initiallySelectedEmoticon - }) { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) - self.initialized = true - } - } - - self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in - }) - } - - func updatePresentationData(_ presentationData: PresentationData) { - guard !self.animatedOut else { - return - } - let previousTheme = self.presentationData.theme - self.presentationData = presentationData - - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(16.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) - - if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) - self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - - if self.animationNode.isPlaying { - if let animationNode = self.animationNode.makeCopy(colors: iconColors(theme: self.presentationData.theme), progress: 0.2) { - let previousAnimationNode = self.animationNode - self.animationNode = animationNode - - animationNode.completion = { [weak previousAnimationNode] in - previousAnimationNode?.removeFromSupernode() - } - animationNode.isUserInteractionEnabled = false - animationNode.frame = previousAnimationNode.frame - previousAnimationNode.supernode?.insertSubnode(animationNode, belowSubnode: previousAnimationNode) - previousAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, removeOnCompletion: false) - animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } else { - self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) - } - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - func dimTapped() { - if self.selectedEmoticon == self.initiallySelectedEmoticon { - self.cancelButtonPressed() - } else { - let alertController = textAlertController(context: self.context, updatedPresentationData: (self.presentationData, .single(self.presentationData)), title: nil, text: self.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in - if let strongSelf = self { - strongSelf.completion?(strongSelf.selectedEmoticon) - } - })], actionLayout: .horizontal, dismissOnOutsideTap: true) - self.present?(alertController) - } - } - - @objc func switchThemePressed() { - self.switchThemeButton.isUserInteractionEnabled = false - Queue.mainQueue().after(0.5) { - self.switchThemeButton.isUserInteractionEnabled = true - } - - self.animateCrossfade(animateIcon: false) - self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) - self.animationNode.playOnce() - - let isDarkAppearance = !self.isDarkAppearance - self.previewTheme?(self.selectedEmoticon, isDarkAppearance) - self.isDarkAppearance = isDarkAppearance - - if isDarkAppearance { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).start() - } else { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).start() - } - } - - private func animateCrossfade(animateIcon: Bool) { - if animateIcon, let snapshotView = self.animationNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.animationNode.frame - self.animationNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.animationNode.view) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - - Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay) { - if let effectView = self.effectNode.view as? UIVisualEffectView { - UIView.animate(withDuration: ChatThemeScreen.themeCrossfadeDuration, delay: 0.0, options: .curveLinear) { - effectView.effect = UIBlurEffect(style: self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark) - } completion: { _ in - } - } - - let previousColor = self.contentBackgroundNode.backgroundColor ?? .clear - self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - self.contentBackgroundNode.layer.animate(from: previousColor.cgColor, to: (self.contentBackgroundNode.backgroundColor ?? .clear).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: ChatThemeScreen.themeCrossfadeDuration) - } - - if let snapshotView = self.contentContainerNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.contentContainerNode.frame - self.contentContainerNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.contentContainerNode.view) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - - self.listNode.forEachVisibleItemNode { node in - if let node = node as? ThemeSettingsThemeItemIconNode { - node.crossfade() - } - } - } - - private var animatedOut = false - func animateIn() { - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - let targetBounds = self.bounds - self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) - self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset) - transition.animateView({ - self.bounds = targetBounds - self.dimNode.position = dimPosition - }) - } - - private var themeSelectionsCount = 0 - private var displayedPreviewTooltip = false - private func maybePresentPreviewTooltip() { - guard !self.displayedPreviewTooltip, !self.animatedOut else { - return - } - - let frame = self.switchThemeButton.view.convert(self.switchThemeButton.bounds, to: self.view) - let currentTimestamp = Int32(Date().timeIntervalSince1970) - - let isDark = self.presentationData.theme.overallDarkAppearance - - let signal: Signal<(Int32, Int32), NoError> - if isDark { - signal = ApplicationSpecificNotice.getChatSpecificThemeLightPreviewTip(accountManager: self.context.sharedContext.accountManager) - } else { - signal = ApplicationSpecificNotice.getChatSpecificThemeDarkPreviewTip(accountManager: self.context.sharedContext.accountManager) - } - - let _ = (signal - |> deliverOnMainQueue).start(next: { [weak self] count, timestamp in - if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { - strongSelf.displayedPreviewTooltip = true - - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in - return .dismiss(consume: false) - })) - - if isDark { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() - } else { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() - } - } - }) - } - - func animateOut(completion: (() -> Void)? = nil) { - self.animatedOut = true - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - self.wrappingScrollNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.dismiss?() - completion?() - } - }) - } - - var passthroughHitTestImpl: ((CGPoint) -> UIView?)? - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - var presentingAlertController = false - self.controller?.forEachController({ c in - if c is AlertController { - presentingAlertController = true - } - return true - }) - - if !presentingAlertController && self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { - if let result = self.passthroughHitTestImpl?(point) { - return result - } else { - return nil - } - } - } - return super.hitTest(point, with: event) - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.statusBar, .input]) - let cleanInsets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - - let bottomInset: CGFloat = 10.0 + cleanInsets.bottom - let titleHeight: CGFloat = 54.0 - let contentHeight = titleHeight + bottomInset + 188.0 - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) - - let sideInset = floor((layout.size.width - width) / 2.0) - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) - let contentFrame = contentContainerFrame - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 11.0 + UIScreenPixel), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight)) - let textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: 31.0), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - - let switchThemeSize = CGSize(width: 44.0, height: 44.0) - let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize) - transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) - transition.updateFrame(node: self.animationContainerNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0)) - transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(), size: self.animationContainerNode.frame.size)) - - let cancelSize = CGSize(width: 44.0, height: 44.0) - let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize) - transition.updateFrame(node: self.cancelButton, frame: cancelFrame) - - let buttonInset: CGFloat = 16.0 - let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight)) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) - transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame) - - var listInsets = UIEdgeInsets() - listInsets.top += layout.safeInsets.left + 12.0 - listInsets.bottom += layout.safeInsets.right + 12.0 - - let contentSize = CGSize(width: contentFrame.width, height: 120.0) - - self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width) - self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight + 6.0) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - } -} diff --git a/submodules/TelegramUI/Sources/EmojiResources.swift b/submodules/TelegramUI/Sources/EmojiResources.swift deleted file mode 100644 index 228f5ea1c9..0000000000 --- a/submodules/TelegramUI/Sources/EmojiResources.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation -import UIKit -import Postbox -import TelegramCore -import Emoji - -func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool { - if !message.text.isEmpty && message.text.containsOnlyEmoji { - if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) { - return false - } - return true - } else { - return false - } -} - -func messageIsElligibleForLargeCustomEmoji(_ message: Message) -> Bool { - let text = message.text.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "") - guard !text.isEmpty && text.containsOnlyEmoji else { - return false - } - let entities = message.textEntitiesAttribute?.entities ?? [] - guard entities.count > 0 else { - return false - } - for entity in entities { - if case let .CustomEmoji(_, fileId) = entity.type { - if let _ = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { - - } else { - return false - } - } else { - return false - } - } - return true -} diff --git a/submodules/TelegramUI/Sources/GridHoleItem.swift b/submodules/TelegramUI/Sources/GridHoleItem.swift deleted file mode 100644 index b603f10cb1..0000000000 --- a/submodules/TelegramUI/Sources/GridHoleItem.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit - -final class GridHoleItem: GridItem { - let section: GridSection? = nil - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - return GridHoleItemNode() - } - - func update(node: GridItemNode) { - } -} - -class GridHoleItemNode: GridItemNode { - private let activityIndicatorView: UIActivityIndicatorView - - override init() { - self.activityIndicatorView = UIActivityIndicatorView(style: .gray) - - super.init() - - self.view.addSubview(self.activityIndicatorView) - self.activityIndicatorView.startAnimating() - } - - override func layout() { - super.layout() - - let size = self.bounds.size - let activityIndicatorSize = self.activityIndicatorView.bounds.size - self.activityIndicatorView.frame = CGRect(origin: CGPoint(x: floor((size.width - activityIndicatorSize.width) / 2.0), y: floor((size.height - activityIndicatorSize.height) / 2.0)), size: activityIndicatorSize) - } -} diff --git a/submodules/TelegramUI/Sources/GridMessageItem.swift b/submodules/TelegramUI/Sources/GridMessageItem.swift deleted file mode 100644 index 5a05b2dc7a..0000000000 --- a/submodules/TelegramUI/Sources/GridMessageItem.swift +++ /dev/null @@ -1,471 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramCore -import Postbox -import SwiftSignalKit -import TelegramPresentationData -import TelegramUIPreferences -import TelegramStringFormatting -import AccountContext -import RadialStatusNode -import PhotoResources -import GridMessageSelectionNode -import ContextUI -import ChatMessageInteractiveMediaBadge -import ChatControllerInteraction - -private func mediaForMessage(_ message: Message) -> Media? { - for media in message.media { - if let media = media as? TelegramMediaImage { - return media - } else if let file = media as? TelegramMediaFile { - if file.mimeType.hasPrefix("audio/") { - return nil - } else if !file.isVideo && file.mimeType.hasPrefix("video/") { - return file - } else { - return file - } - } - } - return nil -} - -private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) -private let mediaBadgeTextColor = UIColor.white - -final class GridMessageItemSection: GridSection { - let height: CGFloat = 36.0 - - fileprivate let theme: PresentationTheme - private let strings: PresentationStrings - private let fontSize: PresentationFontSize - - private let roundedTimestamp: Int32 - private let month: Int32 - private let year: Int32 - - var hashValue: Int { - return self.roundedTimestamp.hashValue - } - - init(timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) { - self.theme = theme - self.strings = strings - self.fontSize = fontSize - - var now = time_t(timestamp) - var timeinfoNow: tm = tm() - localtime_r(&now, &timeinfoNow) - - self.roundedTimestamp = timeinfoNow.tm_year * 100 + timeinfoNow.tm_mon - self.month = timeinfoNow.tm_mon - self.year = timeinfoNow.tm_year - } - - func isEqual(to: GridSection) -> Bool { - if let to = to as? GridMessageItemSection { - return self.roundedTimestamp == to.roundedTimestamp && theme === to.theme - } else { - return false - } - } - - func node() -> ASDisplayNode { - return GridMessageItemSectionNode(theme: self.theme, strings: self.strings, fontSize: self.fontSize, roundedTimestamp: self.roundedTimestamp, month: self.month, year: self.year) - } -} - -final class GridMessageItemSectionNode: ASDisplayNode { - var theme: PresentationTheme - var strings: PresentationStrings - var fontSize: PresentationFontSize - let titleNode: ASTextNode - - init(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, roundedTimestamp: Int32, month: Int32, year: Int32) { - self.theme = theme - self.strings = strings - self.fontSize = fontSize - - self.titleNode = ASTextNode() - self.titleNode.isUserInteractionEnabled = false - - super.init() - - self.backgroundColor = theme.list.plainBackgroundColor.withAlphaComponent(0.9) - - let sectionTitleFont = Font.regular(floor(fontSize.baseDisplaySize * 14.0 / 17.0)) - - let dateText = stringForMonth(strings: strings, month: month, ofYear: year) - self.addSubnode(self.titleNode) - self.titleNode.attributedText = NSAttributedString(string: dateText, font: sectionTitleFont, textColor: theme.list.itemPrimaryTextColor) - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.truncationMode = .byTruncatingTail - } - - override func layout() { - super.layout() - - let bounds = self.bounds - - let titleSize = self.titleNode.measure(CGSize(width: bounds.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude)) - self.titleNode.frame = CGRect(origin: CGPoint(x: 12.0, y: floor((bounds.size.height - titleSize.height) / 2.0)), size: titleSize) - } -} - -final class GridMessageItem: GridItem { - fileprivate let theme: PresentationTheme - private let strings: PresentationStrings - private let context: AccountContext - fileprivate let message: Message - private let controllerInteraction: ChatControllerInteraction - let section: GridSection? - - init(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, context: AccountContext, message: Message, controllerInteraction: ChatControllerInteraction) { - self.theme = theme - self.strings = strings - self.context = context - self.message = message - self.controllerInteraction = controllerInteraction - self.section = GridMessageItemSection(timestamp: message.timestamp, theme: theme, strings: strings, fontSize: fontSize) - } - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - let node = GridMessageItemNode() - if let media = mediaForMessage(self.message) { - node.setup(context: self.context, item: self, media: media, messageId: self.message.id, controllerInteraction: self.controllerInteraction, synchronousLoad: synchronousLoad) - } - return node - } - - func update(node: GridItemNode) { - guard let node = node as? GridMessageItemNode else { - assertionFailure() - return - } - if let media = mediaForMessage(self.message) { - node.setup(context: self.context, item: self, media: media, messageId: self.message.id, controllerInteraction: self.controllerInteraction, synchronousLoad: false) - } - } -} - -final class GridMessageItemNode: GridItemNode { - private var currentState: (AccountContext, Media, CGSize)? - private let containerNode: ContextControllerSourceNode - private let imageNode: TransformImageNode - private(set) var messageId: MessageId? - private var item: GridMessageItem? - private var controllerInteraction: ChatControllerInteraction? - private var statusNode: RadialStatusNode - private let mediaBadgeNode: ChatMessageInteractiveMediaBadge - - private var selectionNode: GridMessageSelectionNode? - - private let fetchStatusDisposable = MetaDisposable() - private let fetchDisposable = MetaDisposable() - private var resourceStatus: MediaResourceStatus? - - override init() { - self.containerNode = ContextControllerSourceNode() - self.imageNode = TransformImageNode() - self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) - let progressDiameter: CGFloat = 40.0 - self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter) - self.statusNode.isUserInteractionEnabled = false - - self.mediaBadgeNode = ChatMessageInteractiveMediaBadge() - self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 50.0, height: 50.0)) - - super.init() - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.imageNode) - self.containerNode.addSubnode(self.mediaBadgeNode) - - self.containerNode.activated = { [weak self] gesture, _ in - guard let strongSelf = self, let item = strongSelf.item, let controllerInteraction = strongSelf.controllerInteraction else { - gesture.cancel() - return - } - controllerInteraction.openMessageContextActions(item.message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) - } - } - - deinit { - self.fetchStatusDisposable.dispose() - self.fetchDisposable.dispose() - } - - override func didLoad() { - super.didLoad() - - self.mediaBadgeNode.pressed = { [weak self] in - self?.progressPressed() - } - - let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) - recognizer.tapActionAtPoint = { _ in - return .waitForSingleTap - } - self.imageNode.view.addGestureRecognizer(recognizer) - } - - func setup(context: AccountContext, item: GridMessageItem, media: Media, messageId: MessageId, controllerInteraction: ChatControllerInteraction, synchronousLoad: Bool) { - self.item = item - - if self.currentState == nil || self.currentState!.0 !== context || !self.currentState!.1.isEqual(to: media) { - 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, userLocation: .peer(item.message.id.peerId), photoReference: .message(message: MessageReference(item.message), media: image), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) - - self.fetchStatusDisposable.set(nil) - self.statusNode.transitionToState(.none, completion: { [weak self] in - self?.statusNode.isHidden = true - }) - self.mediaBadgeNode.isHidden = true - self.resourceStatus = nil - } else if let file = media as? TelegramMediaFile, file.isVideo { - mediaDimensions = file.dimensions?.cgSize - self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(item.message.id.peerId), videoReference: .message(message: MessageReference(item.message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) - - self.mediaBadgeNode.isHidden = false - - self.resourceStatus = nil - self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: messageId, file: file) |> deliverOnMainQueue).startStrict(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 statusState: RadialStatusNodeState - if isStreamable { - statusState = .none - } else { - switch status { - case let .Fetching(_, progress): - let adjustedProgress = max(progress, 0.027) - statusState = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true, animateRotation: true) - case .Local: - statusState = .none - case .Remote, .Paused: - statusState = .download(.white) - } - } - - switch statusState { - case .none: - break - default: - strongSelf.statusNode.isHidden = false - } - - strongSelf.statusNode.transitionToState(statusState, animated: true, completion: { - if let strongSelf = self { - if case .none = statusState { - strongSelf.statusNode.isHidden = true - } - } - }) - - if let duration = file.duration { - let durationString = stringForDuration(Int32(duration)) - - var badgeContent: ChatMessageInteractiveMediaBadgeContent? - var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? - - if isStreamable { - switch status { - case let .Fetching(_, progress): - let progressString = String(format: "%d%%", Int(progress * 100.0)) - badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString), iconName: nil) - mediaDownloadState = .compactFetching(progress: 0.0) - case .Local: - badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil) - case .Remote, .Paused: - badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil) - mediaDownloadState = .compactRemote - } - } else { - badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString), iconName: nil) - } - - strongSelf.mediaBadgeNode.update(theme: item.theme, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) - } - } - })) - if self.statusNode.supernode == nil { - self.imageNode.addSubnode(self.statusNode) - } - } else { - self.mediaBadgeNode.isHidden = true - } - - if let mediaDimensions = mediaDimensions { - self.currentState = (context, media, mediaDimensions) - self.setNeedsLayout() - } - } - - self.messageId = messageId - self.controllerInteraction = controllerInteraction - - self.updateSelectionState(animated: false) - self.updateHiddenMedia() - } - - override func layout() { - super.layout() - - let imageFrame = self.bounds - - self.containerNode.frame = imageFrame - - self.imageNode.frame = imageFrame - - if let item = self.item, let (_, _, mediaDimensions) = self.currentState { - let imageSize = mediaDimensions.aspectFilled(imageFrame.size) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor))() - } - - self.selectionNode?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - let progressDiameter: CGFloat = 40.0 - self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) - - self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: imageFrame.width - 3.0, y: imageFrame.height - 18.0 - 3.0), size: CGSize(width: 50.0, height: 50.0)) - } - - func updateSelectionState(animated: Bool) { - if let messageId = self.messageId, let controllerInteraction = self.controllerInteraction { - if let selectionState = controllerInteraction.selectionState { - guard let item = self.item else { - return - } - - let selected = selectionState.selectedIds.contains(messageId) - - if let selectionNode = self.selectionNode { - selectionNode.updateSelected(selected, animated: animated) - selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - } else { - let selectionNode = GridMessageSelectionNode(theme: item.theme, toggle: { [weak self] value in - if let strongSelf = self, let messageId = strongSelf.messageId { - strongSelf.controllerInteraction?.toggleMessagesSelection([messageId], value) - } - }) - - selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - self.containerNode.addSubnode(selectionNode) - self.selectionNode = selectionNode - selectionNode.updateSelected(selected, animated: false) - if animated { - selectionNode.animateIn() - } - } - } else { - if let selectionNode = self.selectionNode { - self.selectionNode = nil - if animated { - selectionNode.animateOut { [weak selectionNode] in - selectionNode?.removeFromSupernode() - } - } else { - selectionNode.removeFromSupernode() - } - } - } - } - } - - func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - if self.messageId == id { - let imageNode = self.imageNode - return (self.imageNode, self.imageNode.bounds, { [weak self, weak imageNode] in - var statusNodeHidden = false - var accessoryHidden = false - if let strongSelf = self { - statusNodeHidden = strongSelf.statusNode.isHidden - accessoryHidden = strongSelf.mediaBadgeNode.isHidden - strongSelf.statusNode.isHidden = true - strongSelf.mediaBadgeNode.isHidden = true - } - let view = imageNode?.view.snapshotContentTree(unhide: true) - if let strongSelf = self { - strongSelf.statusNode.isHidden = statusNodeHidden - strongSelf.mediaBadgeNode.isHidden = accessoryHidden - } - return (view, nil) - }) - } else { - return nil - } - } - - func updateHiddenMedia() { - if let controllerInteraction = self.controllerInteraction, let messageId = self.messageId, controllerInteraction.hiddenMedia[messageId] != nil { - self.imageNode.isHidden = true - self.mediaBadgeNode.alpha = 0.0 - self.statusNode.alpha = 0.0 - } else { - self.imageNode.isHidden = false - if self.statusNode.alpha < 1.0 { - self.statusNode.alpha = 1.0 - self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - if self.mediaBadgeNode.alpha < 1.0 { - self.mediaBadgeNode.alpha = 1.0 - self.mediaBadgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } - - private func progressPressed() { - guard let controllerInteraction = self.controllerInteraction, let message = self.item?.message else { - return - } - - if let (context, media, _) = self.currentState, let resourceStatus = self.resourceStatus, let file = media as? TelegramMediaFile { - switch resourceStatus { - case .Fetching: - messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) - case .Local: - let _ = controllerInteraction.openMessage(message, .default) - case .Remote, .Paused: - self.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: true).startStrict()) - } - } - } - - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { - guard let controllerInteraction = self.controllerInteraction, let message = self.item?.message else { - return - } - - switch recognizer.state { - case .ended: - if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { - switch gesture { - case .tap: - if let (_, media, _) = self.currentState, let file = media as? TelegramMediaFile { - if isMediaStreamable(message: message, media: file) { - let _ = controllerInteraction.openMessage(message, .default) - } else { - self.progressPressed() - } - } else { - let _ = controllerInteraction.openMessage(message, .default) - } - case .longTap: - break - default: - break - } - } - default: - break - } - } -} diff --git a/submodules/TelegramUI/Sources/InChatPrefetchManager.swift b/submodules/TelegramUI/Sources/InChatPrefetchManager.swift index d87ae27290..b0c86c381c 100644 --- a/submodules/TelegramUI/Sources/InChatPrefetchManager.swift +++ b/submodules/TelegramUI/Sources/InChatPrefetchManager.swift @@ -6,6 +6,7 @@ import TelegramUIPreferences import AccountContext import PhotoResources import UniversalMediaPlayer +import ChatMessageInteractiveMediaNode private final class PrefetchMediaContext { let fetchDisposable = MetaDisposable() diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 3471adcb02..4564d1a6d1 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -366,7 +366,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return false } -func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { +func openChatInstantPageImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { if let (webpage, anchor) = instantPageAndAnchor(message: message) { let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel) @@ -375,7 +375,7 @@ func openChatInstantPage(context: AccountContext, message: Message, sourcePeerTy } } -func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) { +func openChatWallpaperImpl(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) { for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { let _ = (context.sharedContext.resolveUrl(context: context, peerId: nil, url: content.url, skipUrlAuth: true) diff --git a/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift b/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift index 76dd958791..92ec328110 100644 --- a/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift +++ b/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import UniversalMediaPlayer import AccountContext import AppBundle +import InstantVideoRadialStatusNode private let backgroundImage = UIImage(bundleImageName: "Chat/Message/OverlayInstantVideoShadow")?.precomposed() diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 743bd62cfd..092baf8935 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -17,6 +17,7 @@ import UndoUI import ChatPresentationInterfaceState import ChatControllerInteraction import PeerInfoVisualMediaPaneNode +import ChatMessageItemView final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 0bae018f85..13cad18441 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -311,6 +311,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, updateForwardOptionsState: { _ in }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in + }, presentLinkOptions: { _ in }, shareSelectedMessages: { shareMessages() }, updateTextInputStateAndMode: { _ in @@ -2549,7 +2550,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } return ContextController.Items(content: .list(items)) - }, minHeight: nil) + }, minHeight: nil, animated: true) }))) } if strongSelf.searchDisplayController == nil { @@ -2699,7 +2700,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } return ContextController.Items(content: .list(items)) - }, minHeight: nil) + }, minHeight: nil, animated: true) }))) } @@ -2789,7 +2790,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if let foundGalleryMessage = foundGalleryMessage { - openChatInstantPage(context: strongSelf.context, message: foundGalleryMessage, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) + strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: foundGalleryMessage, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) } }, openWallpaper: { _ in }, openTheme: { _ in @@ -5537,7 +5538,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, action: { [weak self] c, f in self?.openReport(type: .default, contextController: c, backAction: { c in if let mainItemsImpl = mainItemsImpl { - c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil) + c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) } }) }))) @@ -6791,7 +6792,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if let contextController = contextController { - contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil) + contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) } else { strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) if let (layout, navigationHeight) = strongSelf.validLayout { @@ -6887,7 +6888,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if let contextController = contextController { - contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil) + contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) } else { strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) if let (layout, navigationHeight) = strongSelf.validLayout { diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index 32e0c379fc..935cf7c5d2 100644 --- a/submodules/TelegramUI/Sources/PollResultsController.swift +++ b/submodules/TelegramUI/Sources/PollResultsController.swift @@ -7,6 +7,7 @@ import ItemListUI import Display import ItemListPeerItem import ItemListPeerActionItem +import TextFormat private let collapsedResultCount: Int = 10 private let collapsedInitialLimit: Int = 10 @@ -425,4 +426,3 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess return controller } - diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 2398ada772..c404dc2487 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -7,6 +7,8 @@ import PhotoResources import StickerResources import Emoji import UniversalMediaPlayer +import ChatMessageInteractiveMediaNode +import ChatMessageAnimatedStickerItemNode private final class PrefetchMediaContext { let fetchDisposable = MetaDisposable() diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index 6965180c4d..4f36095959 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -8,7 +8,7 @@ import AccountContext import ChatControllerInteraction import ChatHistoryEntry -func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNode?, allUpdated: Bool) -> ChatHistoryViewTransition { +func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNodeImpl?, allUpdated: Bool) -> ChatHistoryViewTransition { var mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) let allUpdated = allUpdated || (fromView?.associatedData != toView.associatedData) if reverse { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c377a2cc1b..8d416794c0 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -44,6 +44,9 @@ import PeerSelectionController import LegacyMessageInputPanel import StatisticsUI import ChatHistoryEntry +import ChatMessageItem +import ChatMessageItemImpl +import ChatRecentActionsController private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -1561,7 +1564,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { chatLocation = .peer(id: messages.first!.id.peerId) } - return ChatMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, defaultReaction: nil, isPremium: false, accountPeer: nil, forceInlineReactions: true), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) + return ChatMessageItemImpl(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, defaultReaction: nil, isPremium: false, accountPeer: nil, forceInlineReactions: true), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) } public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { @@ -1600,6 +1603,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } + public func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { + openChatInstantPageImpl(context: context, message: message, sourcePeerType: sourcePeerType, navigationController: navigationController) + } + + public func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) { + openChatWallpaperImpl(context: context, message: message, present: present) + } + public func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController { return recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: context.engine.privacy.webSessions(), websitesOnly: false) } diff --git a/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift index ba7fb6ee99..87270c2649 100644 --- a/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/WebpagePreviewAccessoryPanelNode.swift @@ -82,8 +82,18 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode { self.iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false) } + override public func didLoad() { + super.didLoad() + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + override func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - if self.theme !== theme || self.strings !== strings { + self.updateThemeAndStrings(theme: theme, strings: strings, force: false) + } + + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, force: Bool) { + if self.theme !== theme || self.strings !== strings || force { self.strings = strings if self.theme !== theme { @@ -209,4 +219,21 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode { dismiss() } } + + private var previousTapTimestamp: Double? + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + let timestamp = CFAbsoluteTimeGetCurrent() + if let previousTapTimestamp = self.previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp { + return + } + self.previousTapTimestamp = CFAbsoluteTimeGetCurrent() + self.interfaceInteraction?.presentLinkOptions(self) + Queue.mainQueue().after(1.5) { + self.updateThemeAndStrings(theme: self.theme, strings: self.strings, force: true) + } + + //let _ = ApplicationSpecificNotice.incrementChatReplyOptionsTip(accountManager: self.context.sharedContext.accountManager, count: 3).start() + } + } } diff --git a/submodules/TelegramUI/Sources/module.private.modulemap b/submodules/TelegramUI/Sources/module.private.modulemap deleted file mode 100644 index 0c4e7fe618..0000000000 --- a/submodules/TelegramUI/Sources/module.private.modulemap +++ /dev/null @@ -1,3 +0,0 @@ -module TelegramUIPrivate { - export * -} diff --git a/submodules/TextFormat/Sources/CountNicePercent.swift b/submodules/TextFormat/Sources/CountNicePercent.swift new file mode 100644 index 0000000000..c62314da22 --- /dev/null +++ b/submodules/TextFormat/Sources/CountNicePercent.swift @@ -0,0 +1,77 @@ +import Foundation +import TelegramCore + +private struct PercentCounterItem: Comparable { + var index: Int = 0 + var percent: Int = 0 + var remainder: Int = 0 + + static func <(lhs: PercentCounterItem, rhs: PercentCounterItem) -> Bool { + if lhs.remainder > rhs.remainder { + return true + } else if lhs.remainder < rhs.remainder { + return false + } + return lhs.percent < rhs.percent + } + +} + +private func adjustPercentCount(_ items: [PercentCounterItem], left: Int) -> [PercentCounterItem] { + var left = left + var items = items.sorted(by: <) + var i:Int = 0 + while i != items.count { + let item = items[i] + var j = i + 1 + loop: while j != items.count { + if items[j].percent != item.percent || items[j].remainder != item.remainder { + break loop + } + j += 1 + } + if items[i].remainder == 0 { + break + } + let equal = j - i + if equal <= left { + left -= equal + while i != j { + items[i].percent += 1 + i += 1 + } + } else { + i = j + } + } + return items +} + +public func countNicePercent(votes: [Int], total: Int) -> [Int] { + var result: [Int] = [] + var items: [PercentCounterItem] = [] + for _ in votes { + result.append(0) + items.append(PercentCounterItem()) + } + + let count = votes.count + + var left:Int = 100 + for i in 0 ..< votes.count { + let votes = votes[i] + items[i].index = i + items[i].percent = Int((Float(votes) * 100) / Float(total)) + items[i].remainder = (votes * 100) - (items[i].percent * total) + left -= items[i].percent + } + + if left > 0 && left <= count { + items = adjustPercentCount(items, left: left) + } + for item in items { + result[item.index] = item.percent + } + + return result +} diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index 095371186f..feacfe990d 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -144,7 +144,7 @@ private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType, } } -public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimatedEmojisInText: Int? = nil) -> [MessageTextEntity] { +public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimatedEmojisInText: Int? = nil, generateLinks: Bool = false) -> [MessageTextEntity] { var entities: [MessageTextEntity] = [] text.enumerateAttributes(in: NSRange(location: 0, length: text.length), options: [], using: { attributes, range, _ in @@ -174,6 +174,13 @@ public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimate } } }) + + for entity in generateTextEntities(text.string, enabledTypes: .allUrl) { + if case .Url = entity.type { + entities.append(entity) + } + } + return entities } diff --git a/submodules/TextFormat/Sources/TextFormat.h b/submodules/TextFormat/Sources/TextFormat.h deleted file mode 100644 index dee068c1cc..0000000000 --- a/submodules/TextFormat/Sources/TextFormat.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// TextFormat.h -// TextFormat -// -// Created by Peter on 8/1/19. -// Copyright © 2019 Telegram Messenger LLP. All rights reserved. -// - -#import - -//! Project version number for TextFormat. -FOUNDATION_EXPORT double TextFormatVersionNumber; - -//! Project version string for TextFormat. -FOUNDATION_EXPORT const unsigned char TextFormatVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index c857636578..9247e0794c 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -241,6 +241,8 @@ public final class TextSelectionNode: ASDisplayNode { public var enableTranslate: Bool = true public var enableShare: Bool = true + public var menuSkipCoordnateConversion: Bool = false + public var didRecognizeTap: Bool { return self.recognizer?.didRecognizeTap ?? false } @@ -549,6 +551,9 @@ public final class TextSelectionNode: ASDisplayNode { self.currentRange = nil self.recognizer?.isSelecting = false self.updateSelection(range: nil, animateIn: false) + + self.contextMenu?.dismiss() + self.contextMenu = nil } public func cancelSelection() { @@ -642,7 +647,9 @@ public final class TextSelectionNode: ASDisplayNode { })) } - let contextMenu = ContextMenuController(actions: actions, catchTapsOutside: false, hasHapticFeedback: false) + self.contextMenu?.dismiss() + + let contextMenu = ContextMenuController(actions: actions, catchTapsOutside: false, hasHapticFeedback: false, skipCoordnateConversion: self.menuSkipCoordnateConversion) contextMenu.dismissOnTap = { [weak self] view, point in guard let self else { return true @@ -658,7 +665,12 @@ public final class TextSelectionNode: ASDisplayNode { guard let strongSelf = self, let rootNode = strongSelf.rootNode() else { return nil } - return (strongSelf, completeRect, rootNode, rootNode.bounds.insetBy(dx: 0.0, dy: -100.0)) + + if strongSelf.menuSkipCoordnateConversion { + return (strongSelf, strongSelf.view.convert(completeRect, to: rootNode.view), rootNode, rootNode.bounds) + } else { + return (strongSelf, completeRect, rootNode, rootNode.bounds) + } }, bounce: false)) }