From 8736981248221f4df281a58f6570f01e12c2be63 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Mon, 13 May 2024 20:14:15 +0400 Subject: [PATCH] Message effects improvements --- .../Sources/ViewController.swift | 2 +- .../Sources/AccountContext.swift | 23 + .../Sources/PeerSelectionController.swift | 2 +- ...AttachmentTextInputActionButtonsNode.swift | 35 +- .../AttachmentTextInputPanelNode.swift | 4 +- .../Sources/AttachmentController.swift | 28 +- .../Sources/AttachmentPanel.swift | 70 ++- .../Sources/ChatListSearchContainerNode.swift | 2 +- ...ChatSendMessageActionSheetController.swift | 23 +- .../ChatSendMessageContextScreen.swift | 174 ++++-- .../Sources/MessageItemView.swift | 495 ++++++++++++------ .../Sources/SendButton.swift | 43 +- .../Sources/CreatePollController.swift | 4 +- .../Sources/LocationPickerController.swift | 4 +- submodules/MediaPickerUI/BUILD | 3 + .../Sources/MediaPickerScreen.swift | 86 ++- .../Sources/MediaPickerSelectedListNode.swift | 335 +++++++----- submodules/TelegramApi/Sources/Api0.swift | 4 +- submodules/TelegramApi/Sources/Api27.swift | 32 +- submodules/TelegramApi/Sources/Api35.swift | 17 +- .../TelegramCore/Sources/Authorization.swift | 12 +- .../Sources/State/AccountState.swift | 2 +- .../State/AvailableMessageEffects.swift | 4 - .../ChangeAccountPhoneNumber.swift | 2 +- .../Auth/CancelAccountReset.swift | 2 +- .../PublicHeaders/LottieCpp/RenderTreeNode.h | 9 + .../CompLayers/ShapeCompositionLayer.cpp | 8 +- .../Utility/CompositionLayersInitializer.cpp | 1 - .../Sources/PeerInfoScreen.swift | 2 +- .../Sources/PeerSelectionController.swift | 6 +- .../Sources/PeerSelectionControllerNode.swift | 20 +- .../Sources/PremiumGiftAttachmentScreen.swift | 4 +- .../Sources/ThemeColorsGridController.swift | 4 +- ...StoryItemSetContainerViewSendMessage.swift | 23 +- .../Sources/AttachmentFileController.swift | 4 +- .../Sources/Chat/ChatControllerPaste.swift | 4 +- .../TelegramUI/Sources/ChatController.swift | 11 +- .../ChatControllerForwardMessages.swift | 2 +- .../ChatControllerOpenAttachmentMenu.swift | 10 +- .../Sources/ContactSelectionController.swift | 4 +- .../Sources/WebSearchController.swift | 20 +- .../Sources/WebSearchControllerNode.swift | 6 +- .../WebUI/Sources/WebAppController.swift | 4 +- 43 files changed, 1045 insertions(+), 505 deletions(-) diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index c3ed1fc5db..b3225e661e 100644 --- a/Tests/LottieMetalTest/Sources/ViewController.swift +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -123,7 +123,7 @@ public final class ViewController: UIViewController { if #available(iOS 13.0, *) { self.test = ReferenceCompareTest(view: self.view) } - } else if "".isEmpty { + } else if !"".isEmpty { let cachedAnimation = cacheLottieMetalAnimation(path: filePath)! let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)! diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 20e4a3a547..af981e4e30 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1068,6 +1068,29 @@ public protocol AccountGroupCallContext: AnyObject { public protocol AccountGroupCallContextCache: AnyObject { } +public final class ChatSendMessageActionSheetControllerMessageEffect { + public let id: Int64 + + public init(id: Int64) { + self.id = id + } +} + +public enum ChatSendMessageActionSheetControllerSendMode { + case generic + case silently + case whenOnline +} + +public protocol ChatSendMessageActionSheetControllerSourceSendButtonNode: ASDisplayNode { + func makeCustomContents() -> UIView? +} + +public protocol ChatSendMessageActionSheetController: ViewController { + typealias SendMode = ChatSendMessageActionSheetControllerSendMode + typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect +} + public protocol AccountContext: AnyObject { var sharedContext: SharedAccountContext { get } var account: Account { get } diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index e34b0ba538..3b29588a52 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -147,7 +147,7 @@ public enum PeerSelectionControllerContext { public protocol PeerSelectionController: ViewController { var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set } - var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? { get set } + var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)? { get set } var inProgress: Bool { get set } var customDismiss: (() -> Void)? { get set } } diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift index 10fa313b6e..032008d056 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift @@ -6,8 +6,10 @@ import TelegramCore import TelegramPresentationData import ContextUI import ChatPresentationInterfaceState +import ComponentFlow +import AccountContext -final class AttachmentTextInputActionButtonsNode: ASDisplayNode { +final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode { private let strings: PresentationStrings let sendContainerNode: ASDisplayNode @@ -16,6 +18,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { var sendButtonHasApplyIcon = false var animatingSendButton = false let textNode: ImmediateTextNode + + private var theme: PresentationTheme var sendButtonLongPressed: ((ASDisplayNode, ContextGesture) -> Void)? @@ -31,9 +35,8 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { private var validLayout: CGSize? init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) { - let theme = presentationInterfaceState.theme - let strings = presentationInterfaceState.strings - self.strings = strings + self.theme = presentationInterfaceState.theme + self.strings = presentationInterfaceState.strings self.sendContainerNode = ASDisplayNode() self.sendContainerNode.layer.allowsGroupOpacity = true @@ -64,9 +67,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { } } else { if highlighted { - strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false) - } else if let presentationLayer = strongSelf.sendButton.layer.presentation() { - strongSelf.sendContainerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false) + let transition: Transition = .easeInOut(duration: 0.4) + transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 0.75) + } else { + let transition: Transition = .easeInOut(duration: 0.25) + transition.setScale(layer: strongSelf.sendContainerNode.layer, scale: 1.0) } } } @@ -90,7 +95,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { return } if !strongSelf.sendButtonHasApplyIcon { - strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer) + strongSelf.sendButtonLongPressed?(strongSelf, recognizer) } } @@ -112,7 +117,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.validLayout = size let width: CGFloat - let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: size.height)) + let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) if minimized { width = 44.0 } else { @@ -140,4 +145,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode { self.accessibilityLabel = self.strings.MediaPicker_Send self.accessibilityHint = nil } + + func makeCustomContents() -> UIView? { + if !self.textNode.alpha.isZero { + let textView = ImmediateTextView() + textView.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: self.theme.chat.inputPanel.actionControlForegroundColor) + let textSize = textView.updateLayout(CGSize(width: 100.0, height: 100.0)) + let _ = textSize + textView.frame = self.textNode.frame + return textView + } + return nil + } } diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index e9066fb90d..7f0f36aa49 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -269,7 +269,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? - public var sendMessage: (AttachmentTextInputPanelSendMode) -> Void = { _ in } + public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in } public var updateHeight: (Bool) -> Void = { _ in } private var updatingInputState = false @@ -1843,7 +1843,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS sendPressed(effectiveInputText) return } - self.sendMessage(.generic) + self.sendMessage(.generic, nil) } @objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) { diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 30c441ba9a..5c6ff18ef3 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -14,6 +14,7 @@ import MediaResources import LegacyMessageInputPanel import LegacyMessageInputPanelInputView import AttachmentTextInputPanelNode +import ChatSendMessageActionUI public enum AttachmentButtonType: Equatable { case gallery @@ -97,6 +98,7 @@ public protocol AttachmentContainable: ViewController { var isContainerExpanded: () -> Bool { get set } var isPanGestureEnabled: (() -> Bool)? { get } var mediaPickerContext: AttachmentMediaPickerContext? { get } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get } func isContainerPanningUpdated(_ panning: Bool) @@ -131,6 +133,10 @@ public extension AttachmentContainable { var isPanGestureEnabled: (() -> Bool)? { return nil } + + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { + return nil + } } public enum AttachmentMediaPickerSendMode { @@ -154,8 +160,8 @@ public protocol AttachmentMediaPickerContext { func mainButtonAction() func setCaption(_ caption: NSAttributedString) - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) - func schedule() + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) } private func generateShadowImage() -> UIImage? { @@ -417,17 +423,17 @@ public class AttachmentController: ViewController { } } - self.panel.sendMessagePressed = { [weak self] mode in + self.panel.sendMessagePressed = { [weak self] mode, messageEffect in if let strongSelf = self { switch mode { case .generic: - strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media) + strongSelf.mediaPickerContext?.send(mode: .generic, attachmentMode: .media, messageEffect: messageEffect) case .silent: - strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media) + strongSelf.mediaPickerContext?.send(mode: .silently, attachmentMode: .media, messageEffect: messageEffect) case .schedule: - strongSelf.mediaPickerContext?.schedule() + strongSelf.mediaPickerContext?.schedule(messageEffect: messageEffect) case .whenOnline: - strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media) + strongSelf.mediaPickerContext?.send(mode: .whenOnline, attachmentMode: .media, messageEffect: messageEffect) } } } @@ -455,6 +461,14 @@ public class AttachmentController: ViewController { strongSelf.controller?.presentInGlobalOverlay(c, with: nil) } } + + self.panel.getCurrentSendMessageContextMediaPreview = { [weak self] in + guard let self, let currentController = self.currentControllers.last else { + return nil + } + + return currentController.getCurrentSendMessageContextMediaPreview?() + } } deinit { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index f7b59bff1c..c9a564046b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -728,11 +728,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { var beganTextEditing: () -> Void = {} var textUpdated: (NSAttributedString) -> Void = { _ in } - var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in } + var sendMessagePressed: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.MessageEffect?) -> Void = { _, _ in } var requestLayout: () -> Void = {} var present: (ViewController) -> Void = { _ in } var presentInGlobalOverlay: (ViewController) -> Void = { _ in } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? + var mainButtonPressed: () -> Void = { } init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { @@ -967,20 +969,56 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { sendWhenOnlineAvailable = false } - let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, completion: { - }, sendMessage: { [weak textInputPanelNode] mode, _ in - switch mode { - case .generic: - textInputPanelNode?.sendMessage(.generic) - case .silently: - textInputPanelNode?.sendMessage(.silent) - case .whenOnline: - textInputPanelNode?.sendMessage(.whenOnline) + let mediaPreview = strongSelf.getCurrentSendMessageContextMediaPreview?() + let isReady: Signal + if let mediaPreview { + isReady = mediaPreview.isReady + |> filter { $0 } + |> take(1) + |> timeout(0.5, queue: .mainQueue(), alternate: .single(true)) + } else { + isReady = .single(true) + } + + let _ = (isReady + |> deliverOnMainQueue).start(next: { [weak strongSelf] _ in + guard let strongSelf else { + return } - }, schedule: { [weak textInputPanelNode] _ in - textInputPanelNode?.sendMessage(.schedule) - }, reactionItems: effectItems, availableMessageEffects: availableMessageEffects, isPremium: hasPremium) - strongSelf.presentInGlobalOverlay(controller) + + let controller = makeChatSendMessageActionSheetController( + context: strongSelf.context, + peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, + forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, + hasEntityKeyboard: hasEntityKeyboard, + gesture: gesture, + sourceSendButton: node, + textInputView: textInputNode.textView, + mediaPreview: mediaPreview, + emojiViewProvider: textInputPanelNode.emojiViewProvider, + attachment: true, + canSendWhenOnline: sendWhenOnlineAvailable, + completion: { + }, + sendMessage: { [weak textInputPanelNode] mode, messageEffect in + switch mode { + case .generic: + textInputPanelNode?.sendMessage(.generic, messageEffect) + case .silently: + textInputPanelNode?.sendMessage(.silent, messageEffect) + case .whenOnline: + textInputPanelNode?.sendMessage(.whenOnline, messageEffect) + } + }, + schedule: { [weak textInputPanelNode] messageEffect in + textInputPanelNode?.sendMessage(.schedule, messageEffect) + }, + reactionItems: effectItems, + availableMessageEffects: availableMessageEffects, + isPremium: hasPremium + ) + strongSelf.presentInGlobalOverlay(controller) + }) }) }, openScheduledMessages: { }, openPeersNearby: { @@ -1249,9 +1287,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } }, makeEntityInputView: self.makeEntityInputView) textInputPanelNode.interfaceInteraction = self.interfaceInteraction - textInputPanelNode.sendMessage = { [weak self] mode in + textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in if let strongSelf = self { - strongSelf.sendMessagePressed(mode) + strongSelf.sendMessagePressed(mode, messageEffect) } } textInputPanelNode.focusUpdated = { [weak self] focus in diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 9a7352e41c..9c5c85b55e 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -1340,7 +1340,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: messages.values.filter { messageIds.contains($0.id) }) let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: true, selectForumThreads: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in guard let strongSelf = self, let strongController = peerSelectionController else { return } diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index 71e76349a5..457bec6f6f 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -11,25 +11,6 @@ import TextFormat import ReactionSelectionNode import WallpaperBackgroundNode -public enum ChatSendMessageActionSheetControllerSendMode { - case generic - case silently - case whenOnline -} - -public final class ChatSendMessageActionSheetControllerMessageEffect { - public let id: Int64 - - public init(id: Int64) { - self.id = id - } -} - -public protocol ChatSendMessageActionSheetController: ViewController { - typealias SendMode = ChatSendMessageActionSheetControllerSendMode - typealias MessageEffect = ChatSendMessageActionSheetControllerMessageEffect -} - private final class ChatSendMessageActionSheetControllerImpl: ViewController, ChatSendMessageActionSheetController { private var controllerNode: ChatSendMessageActionSheetControllerNode { return self.displayNode as! ChatSendMessageActionSheetControllerNode @@ -203,6 +184,7 @@ public func makeChatSendMessageActionSheetController( gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, + mediaPreview: ChatSendMessageContextScreenMediaPreview? = nil, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode? = nil, attachment: Bool = false, @@ -214,7 +196,7 @@ public func makeChatSendMessageActionSheetController( availableMessageEffects: AvailableMessageEffects? = nil, isPremium: Bool = false ) -> ChatSendMessageActionSheetController { - if textInputView.text.isEmpty { + if textInputView.text.isEmpty && !"".isEmpty { return ChatSendMessageActionSheetControllerImpl( context: context, updatedPresentationData: updatedPresentationData, @@ -245,6 +227,7 @@ public func makeChatSendMessageActionSheetController( gesture: gesture, sourceSendButton: sourceSendButton, textInputView: textInputView, + mediaPreview: mediaPreview, emojiViewProvider: emojiViewProvider, wallpaperBackgroundNode: wallpaperBackgroundNode, attachment: attachment, diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index 6741d4d617..aab9c29188 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -32,10 +32,21 @@ func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> return targetWindowFrame } +public protocol ChatSendMessageContextScreenMediaPreview: AnyObject { + var isReady: Signal { get } + var view: UIView { get } + var globalClippingRect: CGRect? { get } + + func animateIn(transition: Transition) + func animateOut(transition: Transition) + func update(containerSize: CGSize, transition: Transition) -> CGSize +} + final class ChatSendMessageContextScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let updatedPresentationData: (initial: PresentationData, signal: Signal)? let peerId: EnginePeer.Id? let isScheduledMessages: Bool let forwardMessageIds: [EngineMessage.Id]? @@ -43,6 +54,7 @@ final class ChatSendMessageContextScreenComponent: Component { let gesture: ContextGesture let sourceSendButton: ASDisplayNode let textInputView: UITextView + let mediaPreview: ChatSendMessageContextScreenMediaPreview? let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? let wallpaperBackgroundNode: WallpaperBackgroundNode? let attachment: Bool @@ -56,6 +68,7 @@ final class ChatSendMessageContextScreenComponent: Component { init( context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id?, isScheduledMessages: Bool, forwardMessageIds: [EngineMessage.Id]?, @@ -63,6 +76,7 @@ final class ChatSendMessageContextScreenComponent: Component { gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, + mediaPreview: ChatSendMessageContextScreenMediaPreview?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode?, attachment: Bool, @@ -75,6 +89,7 @@ final class ChatSendMessageContextScreenComponent: Component { isPremium: Bool ) { self.context = context + self.updatedPresentationData = updatedPresentationData self.peerId = peerId self.isScheduledMessages = isScheduledMessages self.forwardMessageIds = forwardMessageIds @@ -82,6 +97,7 @@ final class ChatSendMessageContextScreenComponent: Component { self.gesture = gesture self.sourceSendButton = sourceSendButton self.textInputView = textInputView + self.mediaPreview = mediaPreview self.emojiViewProvider = emojiViewProvider self.wallpaperBackgroundNode = wallpaperBackgroundNode self.attachment = attachment @@ -141,6 +157,7 @@ final class ChatSendMessageContextScreenComponent: Component { private var standaloneReactionAnimation: AnimatedStickerNode? private var isLoadingEffectAnimation: Bool = false + private var isLoadingEffectAnimationTimerDisposable: Disposable? private var loadEffectAnimationDisposable: Disposable? private var presentationAnimationState: PresentationAnimationState = .initial @@ -177,6 +194,7 @@ final class ChatSendMessageContextScreenComponent: Component { deinit { self.messageEffectDisposable.dispose() self.loadEffectAnimationDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable?.dispose() } @objc private func onBackgroundTap(_ recognizer: UITapGestureRecognizer) { @@ -196,6 +214,8 @@ final class ChatSendMessageContextScreenComponent: Component { func animateIn() { if case .initial = self.presentationAnimationState { + HapticFeedback().impact() + self.presentationAnimationState = .animatedIn self.state?.updated(transition: .spring(duration: 0.42)) } @@ -270,7 +290,7 @@ final class ChatSendMessageContextScreenComponent: Component { self.environment = environment self.state = state - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 }) if themeUpdated { self.backgroundView.updateColor( @@ -298,15 +318,6 @@ final class ChatSendMessageContextScreenComponent: Component { let sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) - sendButton.update( - context: component.context, - presentationData: presentationData, - backgroundNode: component.wallpaperBackgroundNode, - isLoadingEffectAnimation: self.isLoadingEffectAnimation, - size: sourceSendButtonFrame.size, - transition: transition - ) - let sendButtonScale: CGFloat switch self.presentationAnimationState { case .initial: @@ -389,7 +400,8 @@ final class ChatSendMessageContextScreenComponent: Component { guard let self, let component = self.component else { return } - component.schedule(nil) + self.animateOutToEmpty = true + component.schedule(self.selectedMessageEffect.flatMap({ ChatSendMessageActionSheetController.MessageEffect(id: $0.id) })) self.environment?.controller()?.dismiss() } ))) @@ -476,10 +488,13 @@ final class ChatSendMessageContextScreenComponent: Component { backgroundNode: component.wallpaperBackgroundNode, textString: textString, sourceTextInputView: component.textInputView as? ChatInputTextView, + emojiViewProvider: component.emojiViewProvider, + sourceMediaPreview: component.mediaPreview, textInsets: messageTextInsets, explicitBackgroundSize: explicitMessageBackgroundSize, maxTextWidth: localSourceTextInputViewFrame.width, maxTextHeight: maxTextHeight, + containerSize: CGSize(width: availableSize.width - 16.0 - 40.0, height: availableSize.height), effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil, transition: transition ) @@ -586,6 +601,8 @@ final class ChatSendMessageContextScreenComponent: Component { self.selectedMessageEffect = nil reactionContextNode.selectedItems = Set([]) self.loadEffectAnimationDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable = nil self.isLoadingEffectAnimation = false if let standaloneReactionAnimation = self.standaloneReactionAnimation { @@ -619,10 +636,16 @@ final class ChatSendMessageContextScreenComponent: Component { } self.loadEffectAnimationDisposable?.dispose() - self.isLoadingEffectAnimation = true - if !self.isUpdating { - self.state?.updated(transition: .easeInOut(duration: 0.2)) - } + self.isLoadingEffectAnimationTimerDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable = (Signal.complete() |> delay(0.2, queue: .mainQueue()) |> deliverOnMainQueue).startStrict(completed: { [weak self] in + guard let self else { + return + } + self.isLoadingEffectAnimation = true + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + }) if let standaloneReactionAnimation = self.standaloneReactionAnimation { self.standaloneReactionAnimation = nil @@ -666,9 +689,9 @@ final class ChatSendMessageContextScreenComponent: Component { dataDisposabke.dispose() } } - #if DEBUG + /*#if DEBUG loadEffectAnimationSignal = loadEffectAnimationSignal |> delay(1.0, queue: .mainQueue()) - #endif + #endif*/ self.loadEffectAnimationDisposable = (loadEffectAnimationSignal |> deliverOnMainQueue).start(completed: { [weak self] in @@ -676,6 +699,8 @@ final class ChatSendMessageContextScreenComponent: Component { return } + self.isLoadingEffectAnimationTimerDisposable?.dispose() + self.isLoadingEffectAnimationTimerDisposable = nil self.isLoadingEffectAnimation = false guard let targetView = self.messageItemView?.effectIconView else { @@ -689,7 +714,11 @@ final class ChatSendMessageContextScreenComponent: Component { #if targetEnvironment(simulator) standaloneReactionAnimation = DirectAnimatedStickerNode() #else - standaloneReactionAnimation = LottieMetalAnimatedStickerNode() + if "".isEmpty { + standaloneReactionAnimation = DirectAnimatedStickerNode() + } else { + standaloneReactionAnimation = LottieMetalAnimatedStickerNode() + } #endif standaloneReactionAnimation.isUserInteractionEnabled = false @@ -728,7 +757,7 @@ final class ChatSendMessageContextScreenComponent: Component { return } //TODO:localize - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + let presentationData = component.updatedPresentationData?.initial ?? component.context.sharedContext.currentPresentationData.with({ $0 }) self.environment?.controller()?.present(UndoOverlayController( presentationData: presentationData, content: .premiumPaywall( @@ -764,9 +793,11 @@ final class ChatSendMessageContextScreenComponent: Component { } } - let sourceActionsStackFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) + let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 44.0), height: sourceSendButtonFrame.height) + var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize) + + let sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) - var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.minX, y: sourceSendButtonFrame.minY), size: sourceSendButtonFrame.size) var readyMessageItemFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 8.0 - messageItemSize.width, y: readySendButtonFrame.maxY - 6.0 - messageItemSize.height), size: messageItemSize) var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) @@ -789,8 +820,13 @@ final class ChatSendMessageContextScreenComponent: Component { let sendButtonFrame: CGRect switch self.presentationAnimationState { case .initial: - messageItemFrame = sourceMessageItemFrame - actionsStackFrame = sourceActionsStackFrame + if component.mediaPreview != nil { + messageItemFrame = readyMessageItemFrame + actionsStackFrame = readyActionsStackFrame + } else { + messageItemFrame = sourceMessageItemFrame + actionsStackFrame = sourceActionsStackFrame + } sendButtonFrame = sourceSendButtonFrame case .animatedOut: if self.animateOutToEmpty { @@ -798,8 +834,13 @@ final class ChatSendMessageContextScreenComponent: Component { actionsStackFrame = readyActionsStackFrame sendButtonFrame = readySendButtonFrame } else { - messageItemFrame = sourceMessageItemFrame - actionsStackFrame = sourceActionsStackFrame + if component.mediaPreview != nil { + messageItemFrame = readyMessageItemFrame + actionsStackFrame = readyActionsStackFrame + } else { + messageItemFrame = sourceMessageItemFrame + actionsStackFrame = sourceActionsStackFrame + } sendButtonFrame = sourceSendButtonFrame } case .animatedIn: @@ -809,6 +850,13 @@ final class ChatSendMessageContextScreenComponent: Component { } transition.setFrame(view: messageItemView, frame: messageItemFrame) + messageItemView.updateClippingRect( + sourceMediaPreview: component.mediaPreview, + isAnimatedIn: self.presentationAnimationState.key == .animatedIn, + localFrame: messageItemFrame, + containerSize: availableSize, + transition: transition + ) transition.setPosition(view: actionsStackNode.view, position: CGPoint(x: actionsStackFrame.maxX, y: actionsStackFrame.minY)) transition.setBounds(view: actionsStackNode.view, bounds: CGRect(origin: CGPoint(), size: actionsStackFrame.size)) @@ -820,9 +868,13 @@ final class ChatSendMessageContextScreenComponent: Component { transition.setAlpha(view: actionsStackNode.view, alpha: 1.0) Transition.immediate.setScale(view: actionsStackNode.view, scale: 1.0) actionsStackNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.42, damping: 104.0) + + messageItemView.animateIn(transition: transition) case .animatedOut: transition.setAlpha(view: actionsStackNode.view, alpha: 0.0) transition.setScale(view: actionsStackNode.view, scale: 0.001) + + messageItemView.animateOut(transition: transition) } } else { switch self.presentationAnimationState { @@ -837,7 +889,11 @@ final class ChatSendMessageContextScreenComponent: Component { if let reactionContextNode = self.reactionContextNode { let size = availableSize - let reactionsAnchorRect = messageItemFrame + var reactionsAnchorRect = messageItemFrame + if component.mediaPreview != nil { + reactionsAnchorRect.size.width += 100.0 + reactionsAnchorRect.origin.x -= 4.0 + } transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) reactionContextNode.updateLayout(size: size, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: false, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition) reactionContextNode.updateIsIntersectingContent(isIntersectingContent: false, transition: .immediate) @@ -856,6 +912,16 @@ final class ChatSendMessageContextScreenComponent: Component { } } + sendButton.update( + context: component.context, + presentationData: presentationData, + backgroundNode: component.wallpaperBackgroundNode, + sourceSendButton: component.sourceSendButton, + isAnimatedIn: self.presentationAnimationState.key == .animatedIn, + isLoadingEffectAnimation: self.isLoadingEffectAnimation, + size: sendButtonFrame.size, + transition: transition + ) transition.setPosition(view: sendButton, position: sendButtonFrame.center) transition.setBounds(view: sendButton, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size)) transition.setScale(view: sendButton, scale: sendButtonScale) @@ -866,21 +932,32 @@ final class ChatSendMessageContextScreenComponent: Component { let backgroundAlpha: CGFloat switch self.presentationAnimationState { case .animatedIn: - if previousAnimationState.key == .initial && self.initializationDisplayLink == nil { - self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in - guard let self else { - return + if previousAnimationState.key == .initial { + if environment.inputHeight != 0.0 { + if self.initializationDisplayLink == nil { + self.initializationDisplayLink = SharedDisplayLinkDriver.shared.add({ [weak self] _ in + guard let self else { + return + } + + self.initializationDisplayLink?.invalidate() + self.initializationDisplayLink = nil + + guard let component = self.component else { + return + } + if component.mediaPreview == nil { + component.textInputView.isHidden = true + } + component.sourceSendButton.isHidden = true + }) } - - self.initializationDisplayLink?.invalidate() - self.initializationDisplayLink = nil - - guard let component = self.component else { - return + } else { + if component.mediaPreview == nil { + component.textInputView.isHidden = true } - component.textInputView.isHidden = true component.sourceSendButton.isHidden = true - }) + } } backgroundAlpha = 1.0 @@ -888,7 +965,9 @@ final class ChatSendMessageContextScreenComponent: Component { backgroundAlpha = 0.0 if self.animateOutToEmpty { - component.textInputView.isHidden = false + if component.mediaPreview == nil { + component.textInputView.isHidden = false + } component.sourceSendButton.isHidden = false transition.setAlpha(view: sendButton, alpha: 0.0) @@ -906,7 +985,9 @@ final class ChatSendMessageContextScreenComponent: Component { } if case let .animatedOut(completion) = self.presentationAnimationState { if let component = self.component, !self.animateOutToEmpty { - component.textInputView.isHidden = false + if component.mediaPreview == nil { + component.textInputView.isHidden = false + } component.sourceSendButton.isHidden = false } completion() @@ -950,6 +1031,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputView: UITextView, + mediaPreview: ChatSendMessageContextScreenMediaPreview?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode?, attachment: Bool, @@ -967,6 +1049,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha context: context, component: ChatSendMessageContextScreenComponent( context: context, + updatedPresentationData: updatedPresentationData, peerId: peerId, isScheduledMessages: isScheduledMessages, forwardMessageIds: forwardMessageIds, @@ -974,6 +1057,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha gesture: gesture, sourceSendButton: sourceSendButton, textInputView: textInputView, + mediaPreview: mediaPreview, emojiViewProvider: emojiViewProvider, wallpaperBackgroundNode: wallpaperBackgroundNode, attachment: attachment, @@ -987,18 +1071,12 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha ), navigationBarAppearance: .none, statusBarStyle: .none, - presentationMode: .default + presentationMode: .default, + updatedPresentationData: updatedPresentationData ) self.lockOrientation = true self.blocksBackgroundWhenInOverlay = true - - /*gesture.externalEnded = { [weak self] _ in - guard let self else { - return - } - self.dismiss() - }*/ } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift index e412c5b76f..809f5dcce8 100644 --- a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift +++ b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift @@ -97,10 +97,12 @@ private final class EffectIcon: Component { textView = ComponentView() self.textView = textView } + let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0) let textSize = textView.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black)) + text: .plain(NSAttributedString(string: text, font: Font.regular(10.0), textColor: .black)), + insets: textInsets )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -138,12 +140,19 @@ final class MessageItemView: UIView { private let textClippingContainer: UIView private var textNode: ChatInputTextNode? + private var customEmojiContainerView: CustomEmojiContainerView? + private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? + + private var mediaPreviewClippingView: UIView? + private var mediaPreview: ChatSendMessageContextScreenMediaPreview? private var effectIcon: ComponentView? var effectIconView: UIView? { return self.effectIcon?.view } + private var effectIconBackgroundView: UIImageView? + private var chatTheme: ChatPresentationThemeData? private var currentSize: CGSize? @@ -167,19 +176,36 @@ final class MessageItemView: UIView { preconditionFailure() } + func animateIn(transition: Transition) { + if let mediaPreview = self.mediaPreview { + mediaPreview.animateIn(transition: transition) + } + } + + func animateOut(transition: Transition) { + if let mediaPreview = self.mediaPreview { + mediaPreview.animateOut(transition: transition) + } + } + func update( context: AccountContext, presentationData: PresentationData, backgroundNode: WallpaperBackgroundNode?, textString: NSAttributedString, sourceTextInputView: ChatInputTextView?, + emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, + sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?, textInsets: UIEdgeInsets, explicitBackgroundSize: CGSize?, maxTextWidth: CGFloat, maxTextHeight: CGFloat, + containerSize: CGSize, effect: AvailableMessageEffects.MessageEffect?, transition: Transition ) -> CGSize { + self.emojiViewProvider = emojiViewProvider + var effectIconSize: CGSize? if let effect { let effectIcon: ComponentView @@ -206,89 +232,6 @@ final class MessageItemView: UIView { ) } - var textCutout: TextNodeCutout? - if let effectIconSize { - textCutout = TextNodeCutout(bottomRight: CGSize(width: effectIconSize.width + 4.0, height: effectIconSize.height)) - } - let _ = textCutout - - let textNode: ChatInputTextNode - if let current = self.textNode { - textNode = current - } else { - textNode = ChatInputTextNode(disableTiling: true) - textNode.textView.isScrollEnabled = false - textNode.isUserInteractionEnabled = false - self.textNode = textNode - self.textClippingContainer.addSubview(textNode.view) - - if let sourceTextInputView { - textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset - } - - let messageAttributedText = NSMutableAttributedString(attributedString: textString) - messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length)) - textNode.attributedText = messageAttributedText - } - - let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor - let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle - if let sourceTextInputView, let textTheme = sourceTextInputView.theme { - switch textTheme.quote.lineStyle { - case .solid: - mappedLineStyle = .solid(color: mainColor) - case .doubleDashed: - mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear) - case .tripleDashed: - mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear) - } - } else { - mappedLineStyle = .solid(color: mainColor) - } - - textNode.textView.theme = ChatInputTextView.Theme( - quote: ChatInputTextView.Theme.Quote( - background: mainColor.withMultipliedAlpha(0.1), - foreground: mainColor, - lineStyle: mappedLineStyle, - codeBackground: mainColor.withMultipliedAlpha(0.1), - codeForeground: mainColor - ) - ) - - let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0) - - var currentRightInset: CGFloat = 0.0 - if let sourceTextInputView { - currentRightInset = sourceTextInputView.currentRightInset - } - let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset) - textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight)) - - let textBoundingRect = textNode.textView.currentTextBoundingRect().integral - let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral - - let textWidth = textBoundingRect.width - let textSize = CGSize(width: textWidth, height: textHeight) - - var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom) - - let effectInset: CGFloat = 12.0 - if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset { - if lastLineBoundingRect != textBoundingRect { - positionedTextSize.height += 11.0 - } else { - positionedTextSize.width += effectInset - } - } - let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom) - - positionedTextSize.height = min(positionedTextSize.height, maxTextHeight) - - let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom) - - let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize) - let chatTheme: ChatPresentationThemeData if let current = self.chatTheme, current.theme === presentationData.theme { chatTheme = current @@ -305,89 +248,329 @@ final class MessageItemView: UIView { maskMode: true, backgroundNode: backgroundNode ) - self.backgroundNode.setType( - type: .outgoing(.None), - highlighted: false, - graphics: themeGraphics, - maskMode: true, - hasWallpaper: true, - transition: transition.containedViewLayoutTransition, - backgroundNode: backgroundNode - ) - let backgroundSize = explicitBackgroundSize ?? size - - let previousSize = self.currentSize - self.currentSize = backgroundSize - - let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0)) - - var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size) - if explicitBackgroundSize != nil, let sourceTextInputView { - textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y - } else { - textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0 - textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y) - } - - transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center) - transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds) - - textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight)) - - if let effectIcon = self.effectIcon, let effectIconSize { - if let effectIconView = effectIcon.view { - var animateIn = false - if effectIconView.superview == nil { - animateIn = true - self.addSubview(effectIconView) - } - let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize) - if animateIn { - if let previousSize { - let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize) - effectIconView.frame = previousEffectIconFrame - } else { - effectIconView.frame = effectIconFrame - } - transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0) - if !transition.animation.isImmediate { - effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) - } - } - - transition.setFrame(view: effectIconView, frame: effectIconFrame) + if let sourceMediaPreview { + let mediaPreviewClippingView: UIView + if let current = self.mediaPreviewClippingView { + mediaPreviewClippingView = current + } else { + mediaPreviewClippingView = UIView() + mediaPreviewClippingView.layer.anchorPoint = CGPoint() + mediaPreviewClippingView.clipsToBounds = true + mediaPreviewClippingView.isUserInteractionEnabled = false + self.mediaPreviewClippingView = mediaPreviewClippingView + self.addSubview(mediaPreviewClippingView) } - } else { - if let effectIcon = self.effectIcon { - self.effectIcon = nil + + if self.mediaPreview !== sourceMediaPreview { + self.mediaPreview?.view.removeFromSuperview() + self.mediaPreview = nil + self.mediaPreview = sourceMediaPreview + if let mediaPreview = self.mediaPreview { + mediaPreviewClippingView.addSubview(mediaPreview.view) + } + } + + let mediaPreviewSize = sourceMediaPreview.update(containerSize: containerSize, transition: transition) + + let backgroundSize = CGSize(width: mediaPreviewSize.width + 7.0, height: mediaPreviewSize.height) + + let mediaPreviewFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mediaPreviewSize) + transition.setFrame(view: sourceMediaPreview.view, frame: mediaPreviewFrame) + + if let effectIcon = self.effectIcon, let effectIconSize { if let effectIconView = effectIcon.view { - let effectIconSize = effectIconView.bounds.size - let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize) + var animateIn = false + if effectIconView.superview == nil { + animateIn = true + self.addSubview(effectIconView) + } + + let effectIconBackgroundView: UIImageView + if let current = self.effectIconBackgroundView { + effectIconBackgroundView = current + } else { + effectIconBackgroundView = UIImageView() + self.effectIconBackgroundView = effectIconBackgroundView + self.insertSubview(effectIconBackgroundView, belowSubview: effectIconView) + } + effectIconBackgroundView.backgroundColor = presentationData.theme.chat.message.mediaDateAndStatusFillColor + + let effectIconBackgroundSize = CGSize(width: effectIconSize.width + 8.0 * 2.0, height: 18.0) + let effectIconBackgroundFrame = CGRect(origin: CGPoint(x: mediaPreviewFrame.maxX - effectIconBackgroundSize.width - 6.0, y: mediaPreviewFrame.maxY - effectIconBackgroundSize.height - 6.0), size: effectIconBackgroundSize) + + let effectIconFrame = CGRect(origin: CGPoint(x: effectIconBackgroundFrame.minX + floor((effectIconBackgroundFrame.width - effectIconSize.width) * 0.5), y: effectIconBackgroundFrame.minY + floor((effectIconBackgroundFrame.height - effectIconSize.height) * 0.5)), size: effectIconSize) + + if animateIn { + effectIconView.frame = effectIconFrame + + effectIconBackgroundView.frame = effectIconBackgroundFrame + effectIconBackgroundView.layer.cornerRadius = effectIconBackgroundFrame.height * 0.5 + + transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0) + if !transition.animation.isImmediate { + effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } + + transition.animateAlpha(view: effectIconBackgroundView, from: 0.0, to: 1.0) + } + transition.setFrame(view: effectIconView, frame: effectIconFrame) - transition.setScale(view: effectIconView, scale: 0.001) - transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in - effectIconView?.removeFromSuperview() + + transition.setFrame(view: effectIconBackgroundView, frame: effectIconBackgroundFrame) + transition.setCornerRadius(layer: effectIconBackgroundView.layer, cornerRadius: effectIconBackgroundFrame.height * 0.5) + } + } else { + if let effectIcon = self.effectIcon { + self.effectIcon = nil + + if let effectIconView = effectIcon.view { + transition.setScale(view: effectIconView, scale: 0.001) + transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in + effectIconView?.removeFromSuperview() + }) + } + } + if let effectIconBackgroundView = self.effectIconBackgroundView { + self.effectIconBackgroundView = nil + transition.setAlpha(view: effectIconBackgroundView, alpha: 0.0, completion: { [weak effectIconBackgroundView] _ in + effectIconBackgroundView?.removeFromSuperview() }) } } - } - - let backgroundAlpha: CGFloat - if explicitBackgroundSize != nil { - backgroundAlpha = 0.0 + + return backgroundSize } else { - backgroundAlpha = 1.0 + let textNode: ChatInputTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ChatInputTextNode(disableTiling: true) + textNode.textView.isScrollEnabled = false + textNode.isUserInteractionEnabled = false + self.textNode = textNode + self.textClippingContainer.addSubview(textNode.view) + + if let sourceTextInputView { + textNode.textView.defaultTextContainerInset = sourceTextInputView.defaultTextContainerInset + } + + let messageAttributedText = NSMutableAttributedString(attributedString: textString) + //messageAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (messageAttributedText.string as NSString).length)) + textNode.attributedText = messageAttributedText + } + + let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor + let mappedLineStyle: ChatInputTextView.Theme.Quote.LineStyle + if let sourceTextInputView, let textTheme = sourceTextInputView.theme { + switch textTheme.quote.lineStyle { + case .solid: + mappedLineStyle = .solid(color: mainColor) + case .doubleDashed: + mappedLineStyle = .doubleDashed(mainColor: mainColor, secondaryColor: .clear) + case .tripleDashed: + mappedLineStyle = .tripleDashed(mainColor: mainColor, secondaryColor: .clear, tertiaryColor: .clear) + } + } else { + mappedLineStyle = .solid(color: mainColor) + } + + textNode.textView.theme = ChatInputTextView.Theme( + quote: ChatInputTextView.Theme.Quote( + background: mainColor.withMultipliedAlpha(0.1), + foreground: mainColor, + lineStyle: mappedLineStyle, + codeBackground: mainColor.withMultipliedAlpha(0.1), + codeForeground: mainColor + ) + ) + + let textPositioningInsets = UIEdgeInsets(top: -5.0, left: 0.0, bottom: -4.0, right: -4.0) + + var currentRightInset: CGFloat = 0.0 + if let sourceTextInputView { + currentRightInset = sourceTextInputView.currentRightInset + } + let textHeight = textNode.textHeightForWidth(maxTextWidth, rightInset: currentRightInset) + textNode.updateLayout(size: CGSize(width: maxTextWidth, height: textHeight)) + + let textBoundingRect = textNode.textView.currentTextBoundingRect().integral + let lastLineBoundingRect = textNode.textView.lastLineBoundingRect().integral + + let textWidth = textBoundingRect.width + let textSize = CGSize(width: textWidth, height: textHeight) + + var positionedTextSize = CGSize(width: textSize.width + textPositioningInsets.left + textPositioningInsets.right, height: textSize.height + textPositioningInsets.top + textPositioningInsets.bottom) + + let effectInset: CGFloat = 12.0 + if effect != nil, lastLineBoundingRect.width > textSize.width - effectInset { + if lastLineBoundingRect != textBoundingRect { + positionedTextSize.height += 11.0 + } else { + positionedTextSize.width += effectInset + } + } + let unclippedPositionedTextHeight = positionedTextSize.height - (textPositioningInsets.top + textPositioningInsets.bottom) + + positionedTextSize.height = min(positionedTextSize.height, maxTextHeight) + + let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom) + + let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize) + + self.backgroundNode.setType( + type: .outgoing(.None), + highlighted: false, + graphics: themeGraphics, + maskMode: true, + hasWallpaper: true, + transition: transition.containedViewLayoutTransition, + backgroundNode: backgroundNode + ) + + let backgroundSize = explicitBackgroundSize ?? size + + let previousSize = self.currentSize + self.currentSize = backgroundSize + + let textClippingContainerFrame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: backgroundSize.width - 1.0 - 7.0, height: backgroundSize.height - 1.0 - 1.0)) + + var textClippingContainerBounds = CGRect(origin: CGPoint(), size: textClippingContainerFrame.size) + if explicitBackgroundSize != nil, let sourceTextInputView { + textClippingContainerBounds.origin.y = sourceTextInputView.contentOffset.y + } else { + textClippingContainerBounds.origin.y = unclippedPositionedTextHeight - backgroundSize.height + 4.0 + textClippingContainerBounds.origin.y = max(0.0, textClippingContainerBounds.origin.y) + } + + transition.setPosition(view: self.textClippingContainer, position: textClippingContainerFrame.center) + transition.setBounds(view: self.textClippingContainer, bounds: textClippingContainerBounds) + + textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight)) + self.updateTextContents() + + if let effectIcon = self.effectIcon, let effectIconSize { + if let effectIconView = effectIcon.view { + var animateIn = false + if effectIconView.superview == nil { + animateIn = true + self.addSubview(effectIconView) + } + let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right + 2.0 - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize) + if animateIn { + if let previousSize { + let previousEffectIconFrame = CGRect(origin: CGPoint(x: previousSize.width - textInsets.right + 2.0 - effectIconSize.width, y: previousSize.height - textInsets.bottom - 2.0 - effectIconSize.height), size: effectIconSize) + effectIconView.frame = previousEffectIconFrame + } else { + effectIconView.frame = effectIconFrame + } + transition.animateAlpha(view: effectIconView, from: 0.0, to: 1.0) + if !transition.animation.isImmediate { + effectIconView.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) + } + } + + transition.setFrame(view: effectIconView, frame: effectIconFrame) + } + } else { + if let effectIcon = self.effectIcon { + self.effectIcon = nil + + if let effectIconView = effectIcon.view { + let effectIconSize = effectIconView.bounds.size + let effectIconFrame = CGRect(origin: CGPoint(x: backgroundSize.width - textInsets.right - effectIconSize.width, y: backgroundSize.height - textInsets.bottom - effectIconSize.height), size: effectIconSize) + transition.setFrame(view: effectIconView, frame: effectIconFrame) + transition.setScale(view: effectIconView, scale: 0.001) + transition.setAlpha(view: effectIconView, alpha: 0.0, completion: { [weak effectIconView] _ in + effectIconView?.removeFromSuperview() + }) + } + } + } + + let backgroundAlpha: CGFloat + if explicitBackgroundSize != nil { + backgroundAlpha = 0.0 + } else { + backgroundAlpha = 1.0 + } + + transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha) + self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha) + self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition) + + return backgroundSize + } + } + + func updateClippingRect( + sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?, + isAnimatedIn: Bool, + localFrame: CGRect, + containerSize: CGSize, + transition: Transition + ) { + if let mediaPreviewClippingView = self.mediaPreviewClippingView, let sourceMediaPreview { + let clippingFrame: CGRect + if !isAnimatedIn, let globalClippingRect = sourceMediaPreview.globalClippingRect { + clippingFrame = self.convert(globalClippingRect, from: nil) + } else { + clippingFrame = CGRect(origin: CGPoint(x: -localFrame.minX, y: -localFrame.minY), size: containerSize) + } + + transition.setPosition(view: mediaPreviewClippingView, position: clippingFrame.origin) + transition.setBounds(view: mediaPreviewClippingView, bounds: CGRect(origin: CGPoint(x: clippingFrame.minX, y: clippingFrame.minY), size: clippingFrame.size)) + } + } + + private func updateTextContents() { + guard let textInputNode = self.textNode else { + return } - transition.setFrame(view: self.backgroundWallpaperNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize)) - transition.setAlpha(view: self.backgroundWallpaperNode.view, alpha: backgroundAlpha) - self.backgroundWallpaperNode.updateFrame(CGRect(origin: CGPoint(), size: backgroundSize), transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: backgroundSize)) - transition.setAlpha(view: self.backgroundNode.view, alpha: backgroundAlpha) - self.backgroundNode.updateLayout(size: backgroundSize, transition: transition.containedViewLayoutTransition) + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] - return backgroundSize + if let attributedText = textInputNode.attributedText { + let beginning = textInputNode.textView.beginningOfDocument + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in + if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) { + let textRects = textInputNode.textView.selectionRects(for: textRange) + for textRect in textRects { + customEmojiRects.append((textRect.rect, value)) + break + } + } + } + }) + } + + if !customEmojiRects.isEmpty { + let customEmojiContainerView: CustomEmojiContainerView + if let current = self.customEmojiContainerView { + customEmojiContainerView = current + } else { + customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in + guard let self, let emojiViewProvider = self.emojiViewProvider else { + return nil + } + return emojiViewProvider(emoji) + }) + customEmojiContainerView.isUserInteractionEnabled = false + textInputNode.textView.addSubview(customEmojiContainerView) + self.customEmojiContainerView = customEmojiContainerView + } + + customEmojiContainerView.update(emojiRects: customEmojiRects) + } else { + if let customEmojiContainerView = self.customEmojiContainerView { + customEmojiContainerView.removeFromSuperview() + self.customEmojiContainerView = nil + } + } } } diff --git a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift index 0efc276b3b..bc4ae3fecd 100644 --- a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift +++ b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift @@ -24,6 +24,9 @@ final class SendButton: HighlightTrackingButton { private let iconView: UIImageView private var activityIndicator: ActivityIndicator? + private var didProcessSourceCustomContent: Bool = false + private var sourceCustomContentView: UIView? + override init(frame: CGRect) { self.containerView = UIView() self.containerView.isUserInteractionEnabled = false @@ -50,12 +53,14 @@ final class SendButton: HighlightTrackingButton { context: AccountContext, presentationData: PresentationData, backgroundNode: WallpaperBackgroundNode?, + sourceSendButton: ASDisplayNode, + isAnimatedIn: Bool, isLoadingEffectAnimation: Bool, size: CGSize, transition: Transition ) { - let innerSize = CGSize(width: 33.0, height: 33.0) - transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floor((size.width - innerSize.width) * 0.5), y: floor((size.height - innerSize.height) * 0.5)), size: innerSize)) + let innerSize = CGSize(width: size.width - 5.5 * 2.0, height: 33.0) + transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - innerSize.width) * 0.5), y: floorToScreenPixels((size.height - innerSize.height) * 0.5)), size: innerSize)) transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: innerSize.height * 0.5) if self.window != nil { @@ -71,7 +76,7 @@ final class SendButton: HighlightTrackingButton { transition.setFrame(view: backgroundContent.view, frame: CGRect(origin: CGPoint(), size: innerSize)) } - if [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor { + if backgroundNode != nil && [.day, .night].contains(presentationData.theme.referenceTheme.baseTheme) && !presentationData.theme.chat.message.outgoing.bubble.withWallpaper.hasSingleFillColor { self.backgroundContent?.isHidden = false self.backgroundLayer.isHidden = true } else { @@ -79,18 +84,44 @@ final class SendButton: HighlightTrackingButton { self.backgroundLayer.isHidden = false } - self.backgroundLayer.backgroundColor = presentationData.theme.list.itemAccentColor.cgColor + self.backgroundLayer.backgroundColor = presentationData.theme.chat.inputPanel.actionControlFillColor.cgColor transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: innerSize)) + if !self.didProcessSourceCustomContent { + self.didProcessSourceCustomContent = true + + if let sourceSendButton = sourceSendButton as? ChatSendMessageActionSheetControllerSourceSendButtonNode { + if let sourceCustomContentView = sourceSendButton.makeCustomContents() { + self.sourceCustomContentView = sourceCustomContentView + self.iconView.superview?.insertSubview(sourceCustomContentView, belowSubview: self.iconView) + } + } + } + if self.iconView.image == nil { self.iconView.image = PresentationResourcesChat.chatInputPanelSendIconImage(presentationData.theme) } + if let sourceCustomContentView = self.sourceCustomContentView { + let sourceCustomContentSize = sourceCustomContentView.bounds.size + let sourceCustomContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - sourceCustomContentSize.width) * 0.5) + UIScreenPixel, y: floorToScreenPixels((innerSize.height - sourceCustomContentSize.height) * 0.5)), size: sourceCustomContentSize) + transition.setPosition(view: sourceCustomContentView, position: sourceCustomContentFrame.center) + transition.setBounds(view: sourceCustomContentView, bounds: CGRect(origin: CGPoint(), size: sourceCustomContentFrame.size)) + transition.setAlpha(view: sourceCustomContentView, alpha: isAnimatedIn ? 0.0 : 1.0) + } + if let icon = self.iconView.image { - let iconFrame = CGRect(origin: CGPoint(x: floor((innerSize.width - icon.size.width) * 0.5), y: floor((innerSize.height - icon.size.height) * 0.5)), size: icon.size) + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((innerSize.width - icon.size.width) * 0.5) - UIScreenPixel, y: floorToScreenPixels((innerSize.height - icon.size.height) * 0.5)), size: icon.size) transition.setPosition(view: self.iconView, position: iconFrame.center) transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setAlpha(view: self.iconView, alpha: isLoadingEffectAnimation ? 0.0 : 1.0) + + let iconViewAlpha: CGFloat + if (self.sourceCustomContentView != nil && !isAnimatedIn) || isLoadingEffectAnimation { + iconViewAlpha = 0.0 + } else { + iconViewAlpha = 1.0 + } + transition.setAlpha(view: self.iconView, alpha: iconViewAlpha) transition.setScale(view: self.iconView, scale: isLoadingEffectAnimation ? 0.001 : 1.0) } diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index 5fa3f3b45a..9cb0a04ed7 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -549,10 +549,10 @@ private final class CreatePollContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } func mainButtonAction() { diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index d6430907db..cdde271e86 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -404,10 +404,10 @@ private final class LocationPickerContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } func mainButtonAction() { diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index 7424ef56db..8a2730e157 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -47,6 +47,9 @@ swift_library( "//submodules/RadialStatusNode", "//submodules/Camera", "//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation", + "//submodules/ChatSendMessageActionUI", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index b67802348d..e23df7541e 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -25,6 +25,7 @@ import Camera import CameraScreen import MediaEditor import ImageObjectSeparation +import ChatSendMessageActionUI final class MediaPickerInteraction { let downloadManager: AssetDownloadManager @@ -32,14 +33,14 @@ final class MediaPickerInteraction { let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void let openDraft: (MediaEditorDraft, UIImage?) -> Void let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Bool - let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void - let schedule: () -> Void + let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void + let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void let dismissInput: () -> Void let selectionState: TGMediaSelectionContext? let editingState: TGMediaEditingContext var hiddenMediaId: String? - init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { + init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.MessageEffect?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { self.downloadManager = downloadManager self.openMedia = openMedia self.openSelectedMedia = openSelectedMedia @@ -199,7 +200,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var presentFilePicker: () -> Void = {} private var completed = false - public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in } + public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in } public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } @@ -212,6 +213,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } + public var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? = nil + private let selectedCollection = Promise(nil) var dismissAll: () -> Void = { } @@ -433,6 +436,49 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } }) } + + controller.getCurrentSendMessageContextMediaPreview = { [weak self] () -> ChatSendMessageContextScreenMediaPreview? in + guard let self else { + return nil + } + guard let controller = self.controller else { + return nil + } + guard let (layout, navigationHeight) = self.validLayout else { + return nil + } + + var persistentItems = false + if case .media = controller.subject { + persistentItems = true + } + let previewNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: true) + let clippingRect = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - navigationHeight - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom - 1.0))) + previewNode.globalClippingRect = self.view.convert(clippingRect, to: nil) + previewNode.interaction = self.controller?.interaction + previewNode.getTransitionView = { [weak self] identifier in + guard let self else { + return nil + } + var node: MediaPickerGridItemNode? + self.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.identifier == identifier { + node = itemNode + } + } + if let node = node { + return (node.view, node.spoilerNode?.dustNode, { [weak node] animateCheckNode in + node?.animateFadeIn(animateCheckNode: animateCheckNode, animateSpoilerNode: false) + }) + } else { + return nil + } + } + let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] + previewNode.updateLayout(size: layout.size, insets: UIEdgeInsets(), items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: .immediate) + + return previewNode + } } deinit { @@ -945,7 +991,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { persistentItems = true } - let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems) + let selectionNode = MediaPickerSelectedListNode(context: controller.context, persistentItems: persistentItems, isExternalPreview: false) selectionNode.alpha = animated ? 0.0 : 1.0 selectionNode.layer.allowsGroupOpacity = true selectionNode.isUserInteractionEnabled = false @@ -992,11 +1038,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if animated { switch displayMode { case .selected: - self.selectionNode?.animateIn(initiated: { [weak self] in + self.selectionNode?.animateIn(transition: .animated(duration: 0.25, curve: .easeInOut), initiated: { [weak self] in self?.updateNavigation(transition: .immediate) }, completion: completion) case .all: - self.selectionNode?.animateOut(completion: completion) + self.selectionNode?.animateOut(transition: .animated(duration: 0.25, curve: .easeInOut), completion: completion) } } else { self.updateNavigation(transition: .immediate) @@ -1099,7 +1145,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return self?.transitionView(for: identifier) }, completed: { [weak self] result, silently, scheduleTime, completion in if let strongSelf = self { - strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion) + strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion) } }, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in self?.currentGalleryParentController = c @@ -1138,7 +1184,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return self?.transitionView(for: identifier) }, completed: { [weak self] result, silently, scheduleTime, completion in if let strongSelf = self { - strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion) + strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, nil, completion) } }, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in self?.currentGalleryParentController = c @@ -1172,7 +1218,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } - fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, completion: @escaping () -> Void) { + fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, messageEffect: ChatSendMessageActionSheetController.MessageEffect?, completion: @escaping () -> Void) { guard let controller = self.controller, !controller.completed else { return } @@ -1200,7 +1246,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return } controller.completed = true - controller.legacyCompletion(signals, silently, scheduleTime, { [weak self] identifier in + controller.legacyCompletion(signals, silently, scheduleTime, messageEffect, { [weak self] identifier in return !asFile ? self?.getItemSnapshot(identifier) : nil }, { [weak self] in completion() @@ -1913,18 +1959,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } else { return false } - }, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in + }, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, messageEffect, completion in if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState, !strongSelf.isDismissing { strongSelf.isDismissing = true if let currentItem = currentItem { selectionState.setItem(currentItem, selected: true) } - strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, completion: completion) + strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, messageEffect: messageEffect, completion: completion) } - }, schedule: { [weak self] in + }, schedule: { [weak self] messageEffect in if let strongSelf = self { strongSelf.presentSchedulePicker(false, { [weak self] time in - self?.interaction?.sendSelected(nil, false, time, true, {}) + self?.interaction?.sendSelected(nil, false, time, true, messageEffect, {}) }) } }, dismissInput: { [weak self] in @@ -2384,7 +2430,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { }, action: { [weak self] _, f in f(.default) - self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, completion: {}) + self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, messageEffect: nil, completion: {}) }))) } if selectionCount > 1 { @@ -2517,12 +2563,12 @@ final class MediaPickerContext: AttachmentMediaPickerContext { self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { - self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {}) + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { + self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, messageEffect, {}) } - func schedule() { - self.controller?.interaction?.schedule() + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { + self.controller?.interaction?.schedule(messageEffect) } func mainButtonAction() { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 1353609243..708318d3e5 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -13,11 +13,15 @@ import MosaicLayout import WallpaperBackgroundNode import AccountContext import ChatMessageBackground +import ChatSendMessageActionUI +import ComponentFlow +import ComponentDisplayAdapters private class MediaPickerSelectedItemNode: ASDisplayNode { let asset: TGMediaEditableItem private let interaction: MediaPickerInteraction? private let enableAnimations: Bool + private let isExternalPreview: Bool private let imageNode: ImageNode private var checkNode: InteractiveCheckNode? @@ -57,10 +61,11 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { private var videoDuration: Double? - init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) { + init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool, isExternalPreview: Bool) { self.asset = asset self.interaction = interaction self.enableAnimations = enableAnimations + self.isExternalPreview = isExternalPreview self.imageNode = ImageNode() self.imageNode.contentMode = .scaleAspectFill @@ -279,7 +284,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if let checkNode = self.checkNode { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - transition.updateAlpha(node: checkNode, alpha: selectionState.count() < 2 ? 0.0 : 1.0) + transition.updateAlpha(node: checkNode, alpha: (self.isExternalPreview || selectionState.count() < 2) ? 0.0 : 1.0) } } } @@ -382,7 +387,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { return view } - func animateFrom(_ view: UIView) { + func animateFrom(_ view: UIView, transition: ContainedViewLayoutTransition) { view.alpha = 0.0 let frame = view.convert(view.bounds, to: self.supernode?.view) @@ -392,13 +397,19 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.updateLayout(size: frame.size, transition: .immediate) - self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) - self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in - view?.alpha = 1.0 + self.updateLayout(size: targetFrame.size, transition: transition) + transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, completion: { [weak self, weak view] _ in + guard let self else { + view?.alpha = 1.0 + return + } + if !self.isExternalPreview { + view?.alpha = 1.0 + } }) } - func animateTo(_ view: UIView, dustNode: ASDisplayNode?, completion: @escaping (Bool) -> Void) { + func animateTo(_ view: UIView, dustNode: ASDisplayNode?, transition: ContainedViewLayoutTransition, completion: @escaping (Bool) -> Void) { view.alpha = 0.0 let frame = self.frame @@ -418,14 +429,13 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { self.addSubnode(dustNode) dustNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - dustNode.layer.animatePosition(from: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - + transition.animatePositionAdditive(layer: dustNode.layer, offset: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), to: dustNode.position) self.spoilerNode?.dustNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } self.corners = [] - self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) - self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in + self.updateLayout(size: targetFrame.size, transition: transition) + transition.animateFrame(layer: self.layer, from: frame, to: targetFrame, removeOnCompletion: false, completion: { [weak view, weak self] _ in view?.alpha = 1.0 self?.durationTextNode?.layer.removeAllAnimations() @@ -471,7 +481,7 @@ private class MessageBackgroundNode: ASDisplayNode { private var absoluteRect: (CGRect, CGSize)? - func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, theme: PresentationTheme, wallpaper: TelegramWallpaper, graphics: PrincipalThemeEssentialGraphics, wallpaperBackgroundNode: WallpaperBackgroundNode?, transition: ContainedViewLayoutTransition) { self.backgroundNode.setType(type: .outgoing(.Extracted), highlighted: false, graphics: graphics, maskMode: false, hasWallpaper: wallpaper.hasWallpaper, transition: transition, backgroundNode: wallpaperBackgroundNode) self.backgroundWallpaperNode.setType(type: .outgoing(.Extracted), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: wallpaperBackgroundNode) self.shadowNode.setType(type: .outgoing(.Extracted), hasWallpaper: wallpaper.hasWallpaper, graphics: graphics) @@ -496,11 +506,13 @@ private class MessageBackgroundNode: ASDisplayNode { } } -final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { +final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate, ChatSendMessageContextScreenMediaPreview { private let context: AccountContext private let persistentItems: Bool + private let isExternalPreview: Bool + var globalClippingRect: CGRect? - fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode + fileprivate var wallpaperBackgroundNode: WallpaperBackgroundNode? private let scrollNode: ASScrollNode private var backgroundNodes: [Int: MessageBackgroundNode] = [:] private var itemNodes: [String: MediaPickerSelectedItemNode] = [:] @@ -518,17 +530,28 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS private var didSetReady = false private var ready = Promise() - init(context: AccountContext, persistentItems: Bool) { + private var contentSize: CGSize = CGSize() + + var isReady: Signal { + return self.ready.get() + } + + init(context: AccountContext, persistentItems: Bool, isExternalPreview: Bool) { self.context = context self.persistentItems = persistentItems + self.isExternalPreview = isExternalPreview - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) - self.wallpaperBackgroundNode.backgroundColor = .black self.scrollNode = ASScrollNode() + self.scrollNode.clipsToBounds = false super.init() - self.addSubnode(self.wallpaperBackgroundNode) + if !self.isExternalPreview { + let wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) + wallpaperBackgroundNode.backgroundColor = .black + self.wallpaperBackgroundNode = wallpaperBackgroundNode + self.addSubnode(wallpaperBackgroundNode) + } self.addSubnode(self.scrollNode) } @@ -578,7 +601,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS var getTransitionView: (String) -> (UIView, ASDisplayNode?, (Bool) -> Void)? = { _ in return nil } - func animateIn(initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) { + func animateIn(transition: ContainedViewLayoutTransition, initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) { let _ = (self.ready.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -589,79 +612,105 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS strongSelf.alpha = 1.0 initiated() - strongSelf.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in + if let wallpaperBackgroundNode = strongSelf.wallpaperBackgroundNode { + wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in + completion() + }) + wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + } else { completion() - }) - strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + } for (_, backgroundNode) in strongSelf.backgroundNodes { backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1) + if strongSelf.isExternalPreview { + Transition.immediate.setScale(layer: backgroundNode.layer, scale: 0.001) + transition.updateTransformScale(layer: backgroundNode.layer, scale: 1.0) + } } for (identifier, itemNode) in strongSelf.itemNodes { if let (transitionView, _, _) = strongSelf.getTransitionView(identifier) { - itemNode.animateFrom(transitionView) + itemNode.animateFrom(transitionView, transition: transition) } else { - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + if strongSelf.isExternalPreview { + itemNode.alpha = 0.0 + transition.animateTransformScale(node: itemNode, from: 0.001) + transition.updateAlpha(node: itemNode, alpha: 1.0) + } else { + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } } } if let topNode = strongSelf.messageNodes?.first, !topNode.alpha.isZero { topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1) - topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(x: 0.0, y: -30.0)) } if let bottomNode = strongSelf.messageNodes?.last, !bottomNode.alpha.isZero { bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1) - bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(x: 0.0, y: 30.0)) } }) } - func animateOut(completion: @escaping () -> Void = {}) { - self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in - completion() - - if let strongSelf = self { - Queue.mainQueue().after(0.01) { - for (_, backgroundNode) in strongSelf.backgroundNodes { - backgroundNode.layer.removeAllAnimations() + func animateOut(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) { + if let wallpaperBackgroundNode = self.wallpaperBackgroundNode{ + wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in + completion() + + if let strongSelf = self { + Queue.mainQueue().after(0.01) { + for (_, backgroundNode) in strongSelf.backgroundNodes { + backgroundNode.layer.removeAllAnimations() + } + + for (_, itemNode) in strongSelf.itemNodes { + itemNode.layer.removeAllAnimations() + } + + strongSelf.messageNodes?.first?.layer.removeAllAnimations() + strongSelf.messageNodes?.last?.layer.removeAllAnimations() + + strongSelf.wallpaperBackgroundNode?.layer.removeAllAnimations() } - - for (_, itemNode) in strongSelf.itemNodes { - itemNode.layer.removeAllAnimations() - } - - strongSelf.messageNodes?.first?.layer.removeAllAnimations() - strongSelf.messageNodes?.last?.layer.removeAllAnimations() - - strongSelf.wallpaperBackgroundNode.layer.removeAllAnimations() } - } - }) - - self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + }) + + wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.33, timingFunction: kCAMediaTimingFunctionSpring) + } else { + completion() + } for (_, backgroundNode) in self.backgroundNodes { backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false) + if self.isExternalPreview { + transition.updateTransformScale(layer: backgroundNode.layer, scale: 0.001) + } } for (identifier, itemNode) in self.itemNodes { if let (transitionView, maybeDustNode, completion) = self.getTransitionView(identifier) { - itemNode.animateTo(transitionView, dustNode: maybeDustNode, completion: completion) + itemNode.animateTo(transitionView, dustNode: maybeDustNode, transition: transition, completion: completion) } else { - itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false) + if self.isExternalPreview { + transition.updateTransformScale(node: itemNode, scale: 0.001) + transition.updateAlpha(node: itemNode, alpha: 0.0) + } else { + itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false) + } } } if let topNode = self.messageNodes?.first { topNode.layer.animateAlpha(from: topNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false) - topNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + transition.animatePositionAdditive(layer: topNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), removeOnCompletion: false) } if let bottomNode = self.messageNodes?.last { bottomNode.layer.animateAlpha(from: bottomNode.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false) - bottomNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + transition.animatePositionAdditive(layer: bottomNode.layer, offset: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), removeOnCompletion: false) } } @@ -782,7 +831,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS if let current = self.itemNodes[identifier] { itemNode = current } else { - itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency) + itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency, isExternalPreview: self.isExternalPreview) self.itemNodes[identifier] = itemNode self.scrollNode.addSubnode(itemNode) @@ -852,55 +901,61 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - var peers = SimpleDictionary() - peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) - - let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview - - let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) - - let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) - - let headerItems: [ListViewItem] = [previewItem, dragItem] - - let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height) - if let messageNodes = self.messageNodes { - for i in 0 ..< headerItems.count { - let itemNode = messageNodes[i] - headerItems[i].updateNode(async: { $0() }, node: { - return itemNode - }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in - let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height)) - - itemNode.contentSize = layout.contentSize - itemNode.insets = layout.insets - itemNode.frame = nodeFrame - itemNode.isUserInteractionEnabled = false - - apply(ListViewItemApply(isOnScreen: true)) - }) + if !self.isExternalPreview { + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + var peers = SimpleDictionary() + peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) + + let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview + + let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) + + let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true, isPreview: true, isStandalone: false) + + let headerItems: [ListViewItem] = [previewItem, dragItem] + + let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height) + if let messageNodes = self.messageNodes { + for i in 0 ..< headerItems.count { + let itemNode = messageNodes[i] + headerItems[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + apply(ListViewItemApply(isOnScreen: true)) + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< headerItems.count { + var itemNode: ListViewItemNode? + headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + self.scrollNode.addSubnode(itemNode!) + } + self.messageNodes = messageNodes } - } else { - var messageNodes: [ListViewItemNode] = [] - for i in 0 ..< headerItems.count { - var itemNode: ListViewItemNode? - headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in - itemNode = node - apply().1(ListViewItemApply(isOnScreen: true)) - }) - itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - itemNode!.isUserInteractionEnabled = false - messageNodes.append(itemNode!) - self.scrollNode.addSubnode(itemNode!) - } - self.messageNodes = messageNodes } let spacing: CGFloat = 8.0 - var contentHeight: CGFloat = 60.0 + var contentHeight: CGFloat = 0.0 + var contentWidth: CGFloat = 0.0 + if !self.isExternalPreview { + contentHeight += 60.0 + } if let previewNode = self.messageNodes?.first { transition.updateFrame(node: previewNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top + 28.0), size: previewNode.frame.size)) @@ -915,26 +970,35 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS var groupIndex = 0 for (items, groupSize) in groupLayouts { - let groupRect = CGRect(origin: CGPoint(x: insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0), y: insets.top + contentHeight), size: groupSize) - - let groupBackgroundNode: MessageBackgroundNode - if let current = self.backgroundNodes[groupIndex] { - groupBackgroundNode = current - } else { - groupBackgroundNode = MessageBackgroundNode() - groupBackgroundNode.displaysAsynchronously = false - self.backgroundNodes[groupIndex] = groupBackgroundNode - self.scrollNode.insertSubnode(groupBackgroundNode, at: 0) + var groupRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top + contentHeight), size: groupSize) + if !self.isExternalPreview { + groupRect.origin.x = insets.left + floorToScreenPixels((size.width - insets.left - insets.right - groupSize.width) / 2.0) } var itemTransition = transition - if groupBackgroundNode.frame.width.isZero { - itemTransition = .immediate - } - - itemTransition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0)) - groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition) + if !self.isExternalPreview { + let groupBackgroundNode: MessageBackgroundNode + if let current = self.backgroundNodes[groupIndex] { + groupBackgroundNode = current + } else { + groupBackgroundNode = MessageBackgroundNode() + groupBackgroundNode.displaysAsynchronously = false + self.backgroundNodes[groupIndex] = groupBackgroundNode + self.scrollNode.insertSubnode(groupBackgroundNode, at: 0) + } + + if groupBackgroundNode.frame.width.isZero { + itemTransition = .immediate + } + + let groupBackgroundFrame = groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0) + itemTransition.updatePosition(node: groupBackgroundNode, position: groupBackgroundFrame.center) + itemTransition.updateBounds(node: groupBackgroundNode, bounds: CGRect(origin: CGPoint(), size: groupBackgroundFrame.size)) + groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition) + } + + var isFirstGroup = true for (item, itemRect, itemPosition) in items { if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] { var corners: CACornerMask = [] @@ -968,7 +1032,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } - contentHeight += groupSize.height + spacing + if isFirstGroup { + isFirstGroup = false + } else { + contentHeight += spacing + } + contentHeight += groupSize.height + contentWidth = max(contentWidth, groupSize.width) groupIndex += 1 } @@ -1032,7 +1102,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS self.updateAbsoluteRects() - self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) + if self.isExternalPreview { + self.scrollNode.view.contentSize = CGSize(width: contentWidth, height: contentHeight) + } else { + self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) + } + + self.contentSize = CGSize(width: contentWidth, height: contentHeight) } func updateSelectionState() { @@ -1047,6 +1123,25 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } } + func animateIn(transition: Transition) { + self.animateIn(transition: transition.containedViewLayoutTransition, initiated: {}, completion: {}) + } + + func animateOut(transition: Transition) { + self.animateOut(transition: transition.containedViewLayoutTransition, completion: {}) + } + + func update(containerSize: CGSize, transition: Transition) -> CGSize { + if var validLayout = self.validLayout { + validLayout.size = containerSize + self.validLayout = validLayout + } + + self.updateItems(transition: transition.containedViewLayoutTransition) + + return self.contentSize + } + func updateLayout(size: CGSize, insets: UIEdgeInsets, items: [TGMediaSelectableItem], grouped: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, transition: ContainedViewLayoutTransition) { let previous = self.validLayout self.validLayout = (size, insets, items, grouped, theme, wallpaper, bubbleCorners) @@ -1068,10 +1163,12 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS } let inset: CGFloat = insets.left == 70 ? insets.left : 0.0 - self.wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false) - self.wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners) - transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height))) - self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition) + if let wallpaperBackgroundNode = self.wallpaperBackgroundNode { + wallpaperBackgroundNode.update(wallpaper: wallpaper, animated: false) + wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners) + transition.updateFrame(node: wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height))) + wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), displayMode: .aspectFill, transition: transition) + } self.updateItems(transition: itemsTransition) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 24db8d6c85..2d2a032b4b 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1134,7 +1134,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) } dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } dict[-196020837] = { return Api.auth.SentCodeType.parse_sentCodeTypeEmailCode($0) } - dict[-444918734] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) } + dict[331943703] = { return Api.auth.SentCodeType.parse_sentCodeTypeFirebaseSms($0) } dict[-1425815847] = { return Api.auth.SentCodeType.parse_sentCodeTypeFlashCall($0) } dict[-648651719] = { return Api.auth.SentCodeType.parse_sentCodeTypeFragmentSms($0) } dict[-2113903484] = { return Api.auth.SentCodeType.parse_sentCodeTypeMissedCall($0) } @@ -1365,7 +1365,7 @@ public extension Api { return parser(reader) } else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found") return nil } } diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 56090bb994..a8f5ef4aa7 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -589,7 +589,7 @@ public extension Api.auth { case sentCodeTypeApp(length: Int32) case sentCodeTypeCall(length: Int32) case sentCodeTypeEmailCode(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?) - case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32) + case sentCodeTypeFirebaseSms(flags: Int32, nonce: Buffer?, playIntegrityNonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32) case sentCodeTypeFlashCall(pattern: String) case sentCodeTypeFragmentSms(url: String, length: Int32) case sentCodeTypeMissedCall(prefix: String, length: Int32) @@ -622,12 +622,13 @@ public extension Api.auth { if Int(flags) & Int(1 << 3) != 0 {serializeInt32(resetAvailablePeriod!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt32(resetPendingDate!, buffer: buffer, boxed: false)} break - case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length): + case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length): if boxed { - buffer.appendInt32(-444918734) + buffer.appendInt32(331943703) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeBytes(nonce!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeBytes(playIntegrityNonce!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeString(receipt!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(pushTimeout!, buffer: buffer, boxed: false)} serializeInt32(length, buffer: buffer, boxed: false) @@ -689,8 +690,8 @@ public extension Api.auth { return ("sentCodeTypeCall", [("length", length as Any)]) case .sentCodeTypeEmailCode(let flags, let emailPattern, let length, let resetAvailablePeriod, let resetPendingDate): return ("sentCodeTypeEmailCode", [("flags", flags as Any), ("emailPattern", emailPattern as Any), ("length", length as Any), ("resetAvailablePeriod", resetAvailablePeriod as Any), ("resetPendingDate", resetPendingDate as Any)]) - case .sentCodeTypeFirebaseSms(let flags, let nonce, let receipt, let pushTimeout, let length): - return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)]) + case .sentCodeTypeFirebaseSms(let flags, let nonce, let playIntegrityNonce, let receipt, let pushTimeout, let length): + return ("sentCodeTypeFirebaseSms", [("flags", flags as Any), ("nonce", nonce as Any), ("playIntegrityNonce", playIntegrityNonce as Any), ("receipt", receipt as Any), ("pushTimeout", pushTimeout as Any), ("length", length as Any)]) case .sentCodeTypeFlashCall(let pattern): return ("sentCodeTypeFlashCall", [("pattern", pattern as Any)]) case .sentCodeTypeFragmentSms(let url, let length): @@ -758,19 +759,22 @@ public extension Api.auth { _1 = reader.readInt32() var _2: Buffer? if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _3: Buffer? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseBytes(reader) } + var _4: String? + if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } var _5: Int32? - _5 = reader.readInt32() + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, receipt: _3, pushTimeout: _4, length: _5!) + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityNonce: _3, receipt: _4, pushTimeout: _5, length: _6!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index b0c62cc5ef..9ecf178194 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -2061,15 +2061,16 @@ public extension Api.functions.auth { } } public extension Api.functions.auth { - static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1940588736) + buffer.appendInt32(-1908857314) serializeInt32(flags, buffer: buffer, boxed: false) serializeString(phoneNumber, buffer: buffer, boxed: false) serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeString(safetyNetToken!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(playIntegrityToken!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeString(iosPushSecret!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("safetyNetToken", String(describing: safetyNetToken)), ("playIntegrityToken", String(describing: playIntegrityToken)), ("iosPushSecret", String(describing: iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { @@ -2095,12 +2096,14 @@ public extension Api.functions.auth { } } public extension Api.functions.auth { - static func resendCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func resendCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, reason: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1056025023) + buffer.appendInt32(-890997469) + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(phoneNumber, buffer: buffer, boxed: false) serializeString(phoneCodeHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.resendCode", parameters: [("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + if Int(flags) & Int(1 << 0) != 0 {serializeString(reason!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "auth.resendCode", parameters: [("flags", String(describing: flags)), ("phoneNumber", String(describing: phoneNumber)), ("phoneCodeHash", String(describing: phoneCodeHash)), ("reason", String(describing: reason))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in let reader = BufferReader(buffer) var result: Api.auth.SentCode? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 38ca9941bc..47b5dfce0d 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -133,7 +133,7 @@ private func sendFirebaseAuthorizationCode(accountManager: AccountManager mapError { _ -> SendFirebaseAuthorizationCodeError in return .generic } @@ -275,7 +275,7 @@ public func sendAuthorizationCode(accountManager: AccountManager map { mapping -> String? in guard let receipt = receipt else { @@ -395,7 +395,7 @@ public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, number: String, apiId: Int32, apiHash: String, hash: String, syncContacts: Bool, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { - return account.network.request(Api.functions.auth.resendCode(phoneNumber: number, phoneCodeHash: hash), automaticFloodWait: false) + return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: number, phoneCodeHash: hash, reason: nil), automaticFloodWait: false) |> mapError { error -> AuthorizationCodeRequestError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -421,7 +421,7 @@ private func internalResendAuthorizationCode(accountManager: AccountManager map { mapping -> String? in guard let receipt = receipt else { @@ -520,7 +520,7 @@ public func resendAuthorizationCode(accountManager: AccountManager mapError { error -> AuthorizationCodeRequestError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -563,7 +563,7 @@ public func resendAuthorizationCode(accountManager: AccountManager map { mapping -> String? in guard let receipt = receipt else { diff --git a/submodules/TelegramCore/Sources/State/AccountState.swift b/submodules/TelegramCore/Sources/State/AccountState.swift index 18757d2fb6..554adf9845 100644 --- a/submodules/TelegramCore/Sources/State/AccountState.swift +++ b/submodules/TelegramCore/Sources/State/AccountState.swift @@ -36,7 +36,7 @@ extension SentAuthorizationCodeType { self = .emailSetupRequired(appleSignInAllowed: (flags & (1 << 0)) != 0) case let .sentCodeTypeFragmentSms(url, length): self = .fragment(url: url, length: length) - case let .sentCodeTypeFirebaseSms(_, _, _, pushTimeout, length): + case let .sentCodeTypeFirebaseSms(_, _, _, _, pushTimeout, length): self = .firebase(pushTimeout: pushTimeout, length: length) case let .sentCodeTypeSmsWord(_, beginning): self = .word(startsWith: beginning) diff --git a/submodules/TelegramCore/Sources/State/AvailableMessageEffects.swift b/submodules/TelegramCore/Sources/State/AvailableMessageEffects.swift index 3d114bbf8b..5842b06a9b 100644 --- a/submodules/TelegramCore/Sources/State/AvailableMessageEffects.swift +++ b/submodules/TelegramCore/Sources/State/AvailableMessageEffects.swift @@ -3,10 +3,6 @@ import TelegramApi import Postbox import SwiftSignalKit -/* - availableEffect flags:# premium_required:flags.3?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:flags.1?long effect_animation_id:flags.2?long = AvailableEffect; - */ - public final class AvailableMessageEffects: Equatable, Codable { public final class MessageEffect: Equatable, Codable { private enum CodingKeys: String, CodingKey { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift index 778b2e986f..c9acfde3c5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/ChangeAccountPhoneNumber.swift @@ -79,7 +79,7 @@ func _internal_requestChangeAccountPhoneNumberVerification(account: Account, pho } func _internal_requestNextChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String, phoneCodeHash: String) -> Signal { - return account.network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false) + return account.network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false) |> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift index 234f771c4f..b0bd632e63 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/CancelAccountReset.swift @@ -41,7 +41,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) -> } func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber: String, phoneCodeHash: String) -> Signal { - return network.request(Api.functions.auth.resendCode(phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash), automaticFloodWait: false) + return network.request(Api.functions.auth.resendCode(flags: 0, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, reason: nil), automaticFloodWait: false) |> mapError { error -> RequestCancelAccountResetDataError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h index daa07c3472..c3a3cd00d7 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -229,6 +229,15 @@ public: CGRect bounds; }; +class RenderTreeNodeContentItem { +public: + RenderTreeNodeContentItem() { + } + +public: + std::vector> subItems; +}; + class RenderTreeNodeContent { public: enum class ShadingType { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp index bc23bcf9b3..096c179443 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -1036,7 +1036,7 @@ public: } public: - void renderChildren(AnimationFrameTime frameTime, std::optional parentTrim) { + void updateChildren(AnimationFrameTime frameTime, std::optional parentTrim) { CATransform3D containerTransform = CATransform3D::identity(); double containerOpacity = 1.0; if (transform) { @@ -1111,7 +1111,7 @@ public: } } - subItems[i]->renderChildren(frameTime, childTrim); + subItems[i]->updateChildren(frameTime, childTrim); } } } @@ -1284,14 +1284,14 @@ CompositionLayer(solidLayer, Vector2D::Zero()) { void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) { _frameTime = frame; _frameTimeInitialized = true; - _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); + _contentTree->itemTree->updateChildren(_frameTime, std::nullopt); } std::shared_ptr ShapeCompositionLayer::renderTreeNode() { if (!_frameTimeInitialized) { _frameTime = 0.0; _frameTimeInitialized = true; - _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); + _contentTree->itemTree->updateChildren(_frameTime, std::nullopt); } if (!_renderTreeNode) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp index bec737cb0a..1685790650 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp @@ -19,7 +19,6 @@ std::vector> initializeCompositionLayers( std::vector> compositionLayers; std::map> layerMap; - /// Organize the assets into a dictionary of [ID : ImageAsset] std::vector> childLayers; for (const auto &layer : layers) { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 763117fce0..35c141cf27 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -10212,7 +10212,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro func forwardMessages(messageIds: Set?) { if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions in + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in guard let strongSelf = self, let strongController = peerSelectionController else { return } diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index 29bb255230..9a9812b207 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -19,7 +19,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private var customTitle: String? public var peerSelected: ((EnginePeer, Int64?) -> Void)? - public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? + public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)? private let filter: ChatListNodePeersFilter private let forumPeerId: EnginePeer.Id? private let selectForumThreads: Bool @@ -246,8 +246,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.peerSelectionNode.navigationBar = self.navigationBar - self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState in - self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState) + self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState, messageEffect in + self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState, messageEffect) } self.peerSelectionNode.requestDeactivateSearch = { [weak self] in diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 8783e49359..5ae13bf4dc 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -83,7 +83,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { var requestOpenDisabledPeer: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)? var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)? var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)? - var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?) -> Void)? + var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.MessageEffect?) -> Void)? private var presentationData: PresentationData { didSet { @@ -529,7 +529,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) } strongSelf.updateChatPresentationInterfaceState(animated: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) }) } - strongSelf.textInputPanelNode?.sendMessage(.generic) + strongSelf.textInputPanelNode?.sendMessage(.generic, nil) f(.default) }))) @@ -692,17 +692,17 @@ final class PeerSelectionControllerNode: ASDisplayNode { } let controller = makeChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, canSendWhenOnline: false, completion: { - }, sendMessage: { [weak textInputPanelNode] mode, _ in + }, sendMessage: { [weak textInputPanelNode] mode, messageEffect in switch mode { case .generic: - textInputPanelNode?.sendMessage(.generic) + textInputPanelNode?.sendMessage(.generic, messageEffect) case .silently: - textInputPanelNode?.sendMessage(.silent) + textInputPanelNode?.sendMessage(.silent, messageEffect) case .whenOnline: - textInputPanelNode?.sendMessage(.whenOnline) + textInputPanelNode?.sendMessage(.whenOnline, messageEffect) } - }, schedule: { [weak textInputPanelNode] _ in - textInputPanelNode?.sendMessage(.schedule) + }, schedule: { [weak textInputPanelNode] messageEffect in + textInputPanelNode?.sendMessage(.schedule, messageEffect) }) strongSelf.presentInGlobalOverlay(controller, nil) }, openScheduledMessages: { @@ -825,7 +825,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return nil }) textInputPanelNode.interfaceInteraction = self.interfaceInteraction - textInputPanelNode.sendMessage = { [weak self] mode in + textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in guard let strongSelf = self else { return } @@ -835,7 +835,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers if !selectedPeers.isEmpty { - strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState) + strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect) } } self.addSubnode(textInputPanelNode) diff --git a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift index 6232f869d9..092f4b8b73 100644 --- a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift @@ -49,10 +49,10 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } func mainButtonAction() { diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 5892427c04..de6c53c741 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -377,10 +377,10 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } func mainButtonAction() { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 69539761e3..74f3fa7f11 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -1556,14 +1556,14 @@ final class StoryItemSetContainerSendMessage { completion(controller, mediaPickerContext) }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in attachmentController?.mediaPickerContext = mediaPickerContext - }, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + }, completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in guard let self, let view else { return } if !inputText.string.isEmpty { self.clearInputText(view: view) } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) } ) case .file: @@ -1875,7 +1875,7 @@ final class StoryItemSetContainerSendMessage { bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, - completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void + completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void ) { guard let component = view.component else { return @@ -1936,8 +1936,8 @@ final class StoryItemSetContainerSendMessage { } return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller) } - controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in - completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion) + controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in + completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion) } present(controller, mediaPickerContext) } @@ -2240,14 +2240,14 @@ final class StoryItemSetContainerSendMessage { present(controller, mediaPickerContext) }, updateMediaPickerContext: { _ in }, - completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + completion: { [weak self, weak view] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in guard let self, let view else { return } if !inputText.string.isEmpty { self.clearInputText(view: view) } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) } ) } @@ -2569,7 +2569,7 @@ final class StoryItemSetContainerSendMessage { } } - private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { + private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { guard let component = view.component else { return } @@ -2620,6 +2620,13 @@ final class StoryItemSetContainerSendMessage { } } } + if let messageEffect { + message = message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(EffectMessageAttribute(id: messageEffect.id)) + return attributes + } + } mappedMessages.append(message) } diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index c1c1126a1c..aaecc8de2b 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -183,10 +183,10 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } func mainButtonAction() { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift index 12bf6ea577..6d9f2abfe6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -27,8 +27,8 @@ extension ChatControllerImpl { subjects: subjects, presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in if let strongSelf = self { - strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) }) } }, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 745b21d29b..6395962f23 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9058,7 +9058,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { + func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageActionSheetController.MessageEffect? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!) |> deliverOnMainQueue).startStrict(next: { [weak self] items in if let strongSelf = self { @@ -9112,6 +9112,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G usedCorrelationId = correlationId completionImpl = nil } + + if let messageEffect { + message = message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(EffectMessageAttribute(id: messageEffect.id)) + return attributes + } + } + mappedMessages.append(message) } diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 6669d37714..628ebe7671 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -90,7 +90,7 @@ extension ChatControllerImpl { }), in: .current) } } - controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions in + controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in guard let strongSelf = self, let strongController = controller else { return } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 88d5c47191..4e12aea7cb 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -308,11 +308,11 @@ extension ChatControllerImpl { completion(controller, mediaPickerContext) }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in attachmentController?.mediaPickerContext = mediaPickerContext - }, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in + }, completion: { [weak self] signals, silentPosting, scheduleTime, messageEffect, getAnimatedTransitionSource, completion in if !inputText.string.isEmpty { self?.clearInputText() } - self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) }) case .file: strongSelf.controllerNavigationDisposable.set(nil) @@ -1136,7 +1136,7 @@ extension ChatControllerImpl { self.present(actionSheet, in: .window(.root)) } - func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { isScheduledMessages = true @@ -1210,8 +1210,8 @@ extension ChatControllerImpl { controller.getCaptionPanelView = { [weak self] in return self?.getCaptionPanelView(isFile: false) } - controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in - completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion) + controller.legacyCompletion = { signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion in + completion(signals, silently, scheduleTime, messageEffect, getAnimatedTransitionSource, sendCompletion) } present(controller, mediaPickerContext) } diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 65cf9921a3..0e16ad6069 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -451,11 +451,11 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { self.controller?.caption = caption } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { self.controller?.contactsNode.requestMultipleAction?(mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil) } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { self.controller?.presentScheduleTimePicker ({ time in self.controller?.contactsNode.requestMultipleAction?(false, time) }) diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index cbd8e9554b..896f70eaff 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -35,14 +35,14 @@ final class WebSearchControllerInteraction { let setSearchQuery: (String) -> Void let deleteRecentQuery: (String) -> Void let toggleSelection: (ChatContextResult, Bool) -> Bool - let sendSelected: (ChatContextResult?, Bool, Int32?) -> Void - let schedule: () -> Void + let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void + let schedule: (ChatSendMessageActionSheetController.MessageEffect?) -> Void let avatarCompleted: (UIImage) -> Void let selectionState: TGMediaSelectionContext? let editingState: TGMediaEditingContext var hiddenMediaId: String? - init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?) -> Void, schedule: @escaping () -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { + init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.MessageEffect?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.MessageEffect?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { self.openResult = openResult self.setSearchQuery = setSearchQuery self.deleteRecentQuery = deleteRecentQuery @@ -254,7 +254,7 @@ public final class WebSearchController: ViewController { } else { return false } - }, sendSelected: { [weak self] current, silently, scheduleTime in + }, sendSelected: { [weak self] current, silently, scheduleTime, messageEffect in if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults { if let current = current { let currentItem = LegacyWebSearchItem(result: current) @@ -264,10 +264,10 @@ public final class WebSearchController: ViewController { sendSelected(results, selectionState, editingState, false) } } - }, schedule: { [weak self] in + }, schedule: { [weak self] messageEffect in if let strongSelf = self { strongSelf.presentSchedulePicker(false, { [weak self] time in - self?.controllerInteraction?.sendSelected(nil, false, time) + self?.controllerInteraction?.sendSelected(nil, false, time, nil) }) } }, avatarCompleted: { result in @@ -606,12 +606,12 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) } - public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { - self.interaction?.sendSelected(nil, mode == .silently, nil) + public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { + self.interaction?.sendSelected(nil, mode == .silently, nil, messageEffect) } - public func schedule() { - self.interaction?.schedule() + public func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { + self.interaction?.schedule(messageEffect) } public func mainButtonAction() { diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index 77f646c76d..b6ca23e4bb 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -717,7 +717,7 @@ class WebSearchControllerNode: ASDisplayNode { } @objc private func sendPressed() { - self.controllerInteraction.sendSelected(nil, false, nil) + self.controllerInteraction.sendSelected(nil, false, nil, nil) self.cancel?() } @@ -740,7 +740,7 @@ class WebSearchControllerNode: ASDisplayNode { return self?.transitionNode(for: result)?.transitionView() }, completed: { [weak self] result in if let strongSelf = self { - strongSelf.controllerInteraction.sendSelected(result, false, nil) + strongSelf.controllerInteraction.sendSelected(result, false, nil, nil) strongSelf.cancel?() } }, getCaptionPanelView: self.getCaptionPanelView, present: present) @@ -760,7 +760,7 @@ class WebSearchControllerNode: ASDisplayNode { }, baseNavigationController: nil, sendCurrent: { [weak self] result in if let strongSelf = self { - strongSelf.controllerInteraction.sendSelected(result, false, nil) + strongSelf.controllerInteraction.sendSelected(result, false, nil, nil) strongSelf.cancel?() } }) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 51b3bcf008..71fc230909 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -2092,10 +2092,10 @@ final class WebAppPickerContext: AttachmentMediaPickerContext { func setCaption(_ caption: NSAttributedString) { } - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) { + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } - func schedule() { + func schedule(messageEffect: ChatSendMessageActionSheetController.MessageEffect?) { } func mainButtonAction() {