From 0aac8d47ac3e8a52bebfe263733179371c0cabb6 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Mon, 23 Mar 2026 21:33:34 +0800 Subject: [PATCH 1/4] Temp --- .../Sources/AccountContext.swift | 12 ++-- .../Messages/TelegramEngineMessages.swift | 20 ++++++ .../TelegramEnginePreferences.swift | 2 + .../Sources/CreateBotScreen.swift | 30 ++++++--- .../Components/TextProcessingScreen/BUILD | 1 + .../Sources/TextProcessingScreen.swift | 54 ++++++++++++++-- .../Sources/ChatControllerNode.swift | 1 + .../TelegramUI/Sources/OpenResolvedUrl.swift | 5 +- .../Sources/SharedAccountContext.swift | 19 ++++++ .../Sources/PostboxKeys.swift | 8 +++ .../WebUI/Sources/WebAppController.swift | 64 +++++++++++++++++++ 11 files changed, 197 insertions(+), 19 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 39d1645bff..e0bd220c63 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1537,14 +1537,18 @@ public protocol SharedAccountContext: AnyObject { func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController - func makeChatCustomRankSetupScreen(context: AccountContext, peerId: EnginePeer.Id, participantId: EnginePeer.Id, rank: String?, role: ChatRankInfoScreenRole) -> ViewController - func makePeerCopyProtectionInfoScreen(context: AccountContext, completion: @escaping () -> Void) -> ViewController - func makeChatRankInfoScreen(context: AccountContext, chatPeer: EnginePeer, userPeer: EnginePeer, role: ChatRankInfoScreenRole, rank: String, canChange: Bool, completion: @escaping () -> Void) -> ViewController - func makeChatRankPreviewItem(context: AccountContext, peer: EnginePeer, rank: String, rankRole: ChatRankInfoScreenRole, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, sectionId: Int32) -> ListViewItem + func makeCreateBotScreen( + context: AccountContext, + parentBot: EnginePeer.Id, + initialUsername: String?, + initialTitle: String?, + openAutomatically: Bool, + completion: @escaping (EnginePeer.Id?) -> Void + ) async -> ViewController? func navigateToCurrentCall() var hasOngoingCall: ValuePromise { get } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 48dbc3a2ca..4af384589e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1778,6 +1778,26 @@ public extension TelegramEngine { public func composeAIMessage(text: TextWithEntities, mode: TelegramComposeAIMessageMode) -> Signal { return _internal_composeAIMessage(account: self.account, text: text, mode: mode) } + + public func requestMiniAppButton(peerId: EnginePeer.Id, requestId: String) -> Signal { + let account = self.account + return self.account.postbox.transaction { transaction -> Api.InputUser? in + return transaction.getPeer(peerId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal in + guard let inputUser else { + return .single(nil) + } + return account.network.request(Api.functions.bots.getRequestedWebViewButton(bot: inputUser, webappReqId: requestId)) + |> map { result -> ReplyMarkupButton in + return ReplyMarkupButton(apiButton: result) + } + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Preferences/TelegramEnginePreferences.swift b/submodules/TelegramCore/Sources/TelegramEngine/Preferences/TelegramEnginePreferences.swift index d2aab5d4e0..f16642c7b4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Preferences/TelegramEnginePreferences.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Preferences/TelegramEnginePreferences.swift @@ -2,6 +2,8 @@ import Foundation import SwiftSignalKit import Postbox +public typealias EnginePreferencesEntry = PreferencesEntry + public extension TelegramEngine { final class Preferences { private let account: Account diff --git a/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift b/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift index 9cbb8fa229..b7b5416af8 100644 --- a/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift +++ b/submodules/TelegramUI/Components/CreateBotScreen/Sources/CreateBotScreen.swift @@ -315,17 +315,23 @@ private final class CreateBotSheetComponent: Component { let parentPeer: EnginePeer let initialUsername: String? let initialTitle: String? + let openAutomatically: Bool + let completion: (EnginePeer.Id) -> Void init( context: AccountContext, parentPeer: EnginePeer, initialUsername: String?, - initialTitle: String? + initialTitle: String?, + openAutomatically: Bool, + completion: @escaping (EnginePeer.Id) -> Void ) { self.context = context self.parentPeer = parentPeer self.initialUsername = initialUsername self.initialTitle = initialTitle + self.openAutomatically = openAutomatically + self.completion = completion } static func ==(lhs: CreateBotSheetComponent, rhs: CreateBotSheetComponent) -> Bool { @@ -410,14 +416,14 @@ private final class CreateBotSheetComponent: Component { self.animateOut.invoke(Action { [weak controller, weak navigationController] _ in if let controller, let navigationController { controller.dismiss(completion: { [weak navigationController] in - guard let navigationController else { - return + if component.openAutomatically, let navigationController { + component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + context: context, + chatLocation: .peer(botPeer) + )) } - component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( - navigationController: navigationController, - context: context, - chatLocation: .peer(botPeer) - )) + component.completion(botPeer.id) }) } }) @@ -573,7 +579,9 @@ public class CreateBotScreen: ViewControllerComponentContainer { context: AccountContext, parentBot: EnginePeer.Id, initialUsername: String?, - initialTitle: String? + initialTitle: String?, + openAutomatically: Bool, + completion: @escaping (EnginePeer.Id) -> Void ) async { self.context = context @@ -589,7 +597,9 @@ public class CreateBotScreen: ViewControllerComponentContainer { context: context, parentPeer: parentPeer, initialUsername: initialUsername, - initialTitle: initialTitle + initialTitle: initialTitle, + openAutomatically: openAutomatically, + completion: completion ), navigationBarAppearance: .none, statusBarStyle: .ignore, diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/BUILD b/submodules/TelegramUI/Components/TextProcessingScreen/BUILD index aabef09139..81acdd605e 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/BUILD +++ b/submodules/TelegramUI/Components/TextProcessingScreen/BUILD @@ -39,6 +39,7 @@ swift_library( "//submodules/TelegramUI/Components/ToastComponent", "//submodules/TelegramNotices", "//submodules/Markdown", + "//submodules/TelegramUIPreferences", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index c29de2e3d3..7818a2c4d9 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -22,6 +22,7 @@ import ListActionItemComponent import ToastComponent import TelegramNotices import Markdown +import TelegramUIPreferences final class TextProcessingContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -40,6 +41,7 @@ final class TextProcessingContentComponent: Component { let mode: TextProcessingScreen.Mode let styles: [TelegramComposeAIMessageMode.Style] let inputText: TextWithEntities + let initialEditState: TextProcessingScreen.EditState? let shouldDisplayStyleNotice: Bool let copyCurrentResult: (() -> Void)? let translateChat: ((String) -> Void)? @@ -51,6 +53,7 @@ final class TextProcessingContentComponent: Component { mode: TextProcessingScreen.Mode, styles: [TelegramComposeAIMessageMode.Style], inputText: TextWithEntities, + initialEditState: TextProcessingScreen.EditState?, shouldDisplayStyleNotice: Bool, copyCurrentResult: (() -> Void)?, translateChat: ((String) -> Void)?, @@ -61,6 +64,7 @@ final class TextProcessingContentComponent: Component { self.context = context self.mode = mode self.inputText = inputText + self.initialEditState = initialEditState self.shouldDisplayStyleNotice = shouldDisplayStyleNotice self.copyCurrentResult = copyCurrentResult self.translateChat = translateChat @@ -106,7 +110,6 @@ final class TextProcessingContentComponent: Component { self.addSubview(self.currentContentBackground) self.addSubview(self.currentContentContainer) - self.translateState.resultUpdated = { [weak self] _ in self?.externalStatesUpdated() } @@ -163,6 +166,30 @@ final class TextProcessingContentComponent: Component { component.externalState.nonPremiumFloodTriggered = true #endif*/ } + + private func saveState() { + guard let component = self.component else { + return + } + if case let .edit(saveRestoreStateId, _, _) = component.mode, let saveRestoreStateId { + let mappedMode: Int32 + switch self.currentMode { + case .translate: + mappedMode = 0 + case .stylize: + mappedMode = 1 + case .fix: + mappedMode = 2 + } + + let state = TextProcessingScreen.EditState( + selectedMode: mappedMode + ) + let _ = component.context.engine.preferences.update(id: ApplicationSpecificPreferencesKeys.textProcessingEditingState(peerId: saveRestoreStateId), { _ in + return EnginePreferencesEntry(state) + }) + } + } func update(component: TextProcessingContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true @@ -498,6 +525,7 @@ private final class TextProcessingSheetComponent: Component { let ignoredTranslationLanguages: [String] let styles: [TelegramComposeAIMessageMode.Style] let inputText: TextWithEntities + let initialEditState: TextProcessingScreen.EditState? let shouldDisplayStyleNotice: Bool let copyCurrentResult: ((TextWithEntities) -> Void)? let translateChat: ((String) -> Void)? @@ -508,6 +536,7 @@ private final class TextProcessingSheetComponent: Component { ignoredTranslationLanguages: [String], styles: [TelegramComposeAIMessageMode.Style], inputText: TextWithEntities, + initialEditState: TextProcessingScreen.EditState?, shouldDisplayStyleNotice: Bool, copyCurrentResult: ((TextWithEntities) -> Void)?, translateChat: ((String) -> Void)? @@ -517,6 +546,7 @@ private final class TextProcessingSheetComponent: Component { self.ignoredTranslationLanguages = ignoredTranslationLanguages self.styles = styles self.inputText = inputText + self.initialEditState = initialEditState self.shouldDisplayStyleNotice = shouldDisplayStyleNotice self.copyCurrentResult = copyCurrentResult self.translateChat = translateChat @@ -614,7 +644,7 @@ private final class TextProcessingSheetComponent: Component { } } else { switch component.mode { - case let .edit(completion, send): + case let .edit(_, completion, send): actionButtonTitle = "Apply" performSendAction = send isMainActionEnabled = !self.contentExternalState.isProcessing @@ -663,6 +693,7 @@ private final class TextProcessingSheetComponent: Component { mode: component.mode, styles: component.styles, inputText: component.inputText, + initialEditState: component.initialEditState, shouldDisplayStyleNotice: component.shouldDisplayStyleNotice, copyCurrentResult: component.copyCurrentResult != nil ? { copyCurrentResultImpl() @@ -891,10 +922,18 @@ private final class TextProcessingSheetComponent: Component { public class TextProcessingScreen: ViewControllerComponentContainer { public enum Mode { - case edit(completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?) + case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?) case translate(fromLanguage: String?) } + struct EditState: Codable { + var selectedMode: Int32 + + init(selectedMode: Int32) { + self.selectedMode = selectedMode + } + } + private let context: AccountContext public init( @@ -908,8 +947,14 @@ public class TextProcessingScreen: ViewControllerComponentContainer { self.context = context let styles = await context.engine.messages.composeAIMessageStyles().get() - let shouldDisplayStyleNotice = await ApplicationSpecificNotice.getAITextProcessingStyleSelection(accountManager: context.sharedContext.accountManager).get() < 3 + + var initialEditState: EditState? + if case let .edit(saveRestoreStateId, _, _) = mode, let saveRestoreStateId { + initialEditState = await context.engine.data.get( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.textProcessingEditingState(peerId: saveRestoreStateId)) + ).get()?.get(EditState.self) + } super.init( context: context, @@ -919,6 +964,7 @@ public class TextProcessingScreen: ViewControllerComponentContainer { ignoredTranslationLanguages: ignoredTranslationLanguages, styles: styles, inputText: inputText, + initialEditState: initialEditState, shouldDisplayStyleNotice: shouldDisplayStyleNotice, copyCurrentResult: copyResult, translateChat: translateChat diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index fc7321d581..d350dc8e74 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -4482,6 +4482,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.controller?.push(await TextProcessingScreen( context: self.context, mode: .edit( + saveRestoreStateId: self.chatLocation.peerId, completion: { [weak self] text in guard let self, let controller = self.controller else { return diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index d5ba402831..039930a101 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -1944,7 +1944,10 @@ func openResolvedUrlImpl( context: context, parentBot: parentBot, initialUsername: username, - initialTitle: title + initialTitle: title, + openAutomatically: true, + completion: { _ in + } ) else { return } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 05f452bd1b..677fa2b938 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -100,6 +100,7 @@ import ChatParticipantRightsScreen import PeerCopyProtectionInfoScreen import ChatRankInfoScreen import RankChatPreviewItem +import CreateBotScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -4363,6 +4364,24 @@ public final class SharedAccountContextImpl: SharedAccountContext { ) return RankChatPreviewItem(context: context, systemStyle: .glass, theme: theme, componentTheme: theme, strings: strings, sectionId: sectionId, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, messageItems: [messageItem]) } + + public func makeCreateBotScreen( + context: AccountContext, + parentBot: EnginePeer.Id, + initialUsername: String?, + initialTitle: String?, + openAutomatically: Bool, + completion: @escaping (EnginePeer.Id?) -> Void + ) async -> ViewController? { + return await CreateBotScreen( + context: context, + parentBot: parentBot, + initialUsername: initialUsername, + initialTitle: initialTitle, + openAutomatically: openAutomatically, + completion: completion + ) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index 879c0c608c..d6f3aa3b6f 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -9,6 +9,7 @@ private enum ApplicationSpecificPreferencesKeyValues: Int32 { case widgetSettings = 19 case mediaAutoSaveSettings = 20 case ageVerificationState = 21 + case textProcessingEditingState = 22 } public struct ApplicationSpecificPreferencesKeys { @@ -18,6 +19,13 @@ public struct ApplicationSpecificPreferencesKeys { public static let widgetSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.widgetSettings.rawValue) public static let mediaAutoSaveSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.mediaAutoSaveSettings.rawValue) public static let ageVerificationState = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.ageVerificationState.rawValue) + + public static func textProcessingEditingState(peerId: PeerId) -> ValueBoxKey { + let key = ValueBoxKey(length: 4 + 8) + key.setInt32(0, value: ApplicationSpecificPreferencesKeyValues.textProcessingEditingState.rawValue) + key.setInt64(4, value: peerId.toInt64()) + return key + } } private enum ApplicationSpecificSharedDataKeyValues: Int32 { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 0b7240470d..ececce6801 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1930,6 +1930,10 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controller?.verifyAgeCompletion?(Int(ageValue)) } } + case "web_app_request_chat": + if let json, let requestId = json["req_id"] as? String { + self.requestChat(requestId: requestId) + } default: break } @@ -2299,6 +2303,66 @@ public final class WebAppController: ViewController, AttachmentContainable { }) } + fileprivate func requestChat(requestId: String) { + guard let controller = self.controller, !self.dismissed else { + return + } + let _ = (self.context.engine.messages.requestMiniAppButton(peerId: controller.botId, requestId: requestId) + |> deliverOnMainQueue).startStandalone(next: { [weak self] button in + guard let self, let button else { + return + } + switch button.action { + case let .requestPeer(peerType, buttonId, maxQuantity): + let _ = maxQuantity + + switch peerType { + case let .createBot(createBot): + Task { @MainActor [weak self] in + guard let self, let controller = self.controller else { + return + } + let createBotScreen = await self.context.sharedContext.makeCreateBotScreen( + context: self.context, + parentBot: controller.botId, + initialUsername: createBot.suggestedUsername, + initialTitle: createBot.suggestedName, + openAutomatically: false, + completion: { [weak self] resultId in + guard let self, let controller = self.controller else { + return + } + if let resultId { + let _ = self.context.engine.peers.sendBotRequestedPeer(peerId: controller.botId, requestId: requestId, buttonId: buttonId, requestedPeerIds: [resultId] + ).startStandalone(error: { [weak self] _ in + guard let self else { + return + } + self.webView?.sendEvent(name: "requested_chat_failed", data: nil) + }, completed: { [weak self] in + guard let self else { + return + } + self.webView?.sendEvent(name: "requested_chat_sent", data: nil) + }) + } else { + self.webView?.sendEvent(name: "requested_chat_failed", data: nil) + } + } + ) + if let createBotScreen { + controller.push(createBotScreen) + } + } + default: + break + } + default: + break + } + }) + } + fileprivate func invokeCustomMethod(requestId: String, method: String, params: String) { guard let controller = self.controller, !self.dismissed else { return From 1a17e97a7ce0e941806f8ef3abfd54174bbc7fcc Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Mon, 23 Mar 2026 23:37:31 +0800 Subject: [PATCH 2/4] Temp --- .../Sources/AttachmentPanel.swift | 2 +- ...ChatSendMessageActionSheetController.swift | 4 +- .../ChatSendMessageContextScreen.swift | 22 +-- .../Sources/SendButton.swift | 4 +- .../SyncCore_TelegramMediaAction.swift | 2 +- .../Sources/ButtonComponent.swift | 27 +++- .../Sources/PeerSelectionControllerNode.swift | 2 +- .../Components/TextProcessingScreen/BUILD | 1 + .../Sources/TextProcessingScreen.swift | 151 +++++++++++++++++- ...ChatMessageDisplaySendMessageOptions.swift | 4 +- .../Sources/ChatControllerNode.swift | 2 +- .../UrlHandling/Sources/UrlHandling.swift | 4 +- 12 files changed, 196 insertions(+), 29 deletions(-) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 043f7ae727..903e3b5fd5 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1382,7 +1382,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, - sourceSendButton: node, + sourceSendButton: node.view, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, completion: { diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index 30a4447d7a..1dfac949d0 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -78,8 +78,8 @@ public func makeChatSendMessageActionSheetController( peerId: EnginePeer.Id?, params: SendMessageActionSheetControllerParams, hasEntityKeyboard: Bool, - gesture: ContextGesture, - sourceSendButton: ASDisplayNode, + gesture: ContextGesture?, + sourceSendButton: UIView, textInputView: UITextView, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode? = nil, diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index 5b91c21e3e..db2bc1f4be 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -59,8 +59,8 @@ final class ChatSendMessageContextScreenComponent: Component { let peerId: EnginePeer.Id? let params: SendMessageActionSheetControllerParams let hasEntityKeyboard: Bool - let gesture: ContextGesture - let sourceSendButton: ASDisplayNode + let gesture: ContextGesture? + let sourceSendButton: UIView let textInputView: UITextView let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? let wallpaperBackgroundNode: WallpaperBackgroundNode? @@ -80,8 +80,8 @@ final class ChatSendMessageContextScreenComponent: Component { peerId: EnginePeer.Id?, params: SendMessageActionSheetControllerParams, hasEntityKeyboard: Bool, - gesture: ContextGesture, - sourceSendButton: ASDisplayNode, + gesture: ContextGesture?, + sourceSendButton: UIView, textInputView: UITextView, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode?, @@ -286,7 +286,7 @@ final class ChatSendMessageContextScreenComponent: Component { guard let self, let component = self.component else { return } - let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self) if self.stableSourceSendButtonFrame != stableSourceSendButtonFrame { self.stableSourceSendButtonFrame = stableSourceSendButtonFrame if !self.isUpdating { @@ -331,7 +331,7 @@ final class ChatSendMessageContextScreenComponent: Component { self.mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove?.0 ?? false } - component.gesture.externalUpdated = { [weak self] view, location in + component.gesture?.externalUpdated = { [weak self] view, location in guard let self, let actionsStackNode = self.actionsStackNode else { return } @@ -346,7 +346,7 @@ final class ChatSendMessageContextScreenComponent: Component { } actionsStackNode.highlightGestureMoved(location: self.convert(localPoint, to: actionsStackNode.view)) } - component.gesture.externalEnded = { [weak self] viewAndLocation in + component.gesture?.externalEnded = { [weak self] viewAndLocation in guard let self, let actionsStackNode = self.actionsStackNode else { return } @@ -423,13 +423,13 @@ final class ChatSendMessageContextScreenComponent: Component { let sourceSendButtonFrame: CGRect switch self.presentationAnimationState { case .animatedOut: - sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self) self.stableSourceSendButtonFrame = sourceSendButtonFrame default: if let stableSourceSendButtonFrame = self.stableSourceSendButtonFrame { sourceSendButtonFrame = stableSourceSendButtonFrame } else { - sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self) self.stableSourceSendButtonFrame = sourceSendButtonFrame } } @@ -1431,8 +1431,8 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha peerId: EnginePeer.Id?, params: SendMessageActionSheetControllerParams, hasEntityKeyboard: Bool, - gesture: ContextGesture, - sourceSendButton: ASDisplayNode, + gesture: ContextGesture?, + sourceSendButton: UIView, textInputView: UITextView, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode?, diff --git a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift index dda0b206c5..d089825eac 100644 --- a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift +++ b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift @@ -64,7 +64,7 @@ final class SendButton: HighlightTrackingButton { context: AccountContext, presentationData: PresentationData, backgroundNode: WallpaperBackgroundNode?, - sourceSendButton: ASDisplayNode, + sourceSendButton: UIView, isAnimatedIn: Bool, isLoadingEffectAnimation: Bool, size: CGSize, @@ -93,7 +93,7 @@ final class SendButton: HighlightTrackingButton { self.sourceCustomContentView = nil } - if let sourceSendButton = sourceSendButton as? ChatSendMessageActionSheetControllerSourceSendButtonNode { + if let sourceSendButton = sourceSendButton.asyncdisplaykit_node as? ChatSendMessageActionSheetControllerSourceSendButtonNode { if let sourceCustomContentView = sourceSendButton.makeCustomContents() { self.sourceCustomContentView = sourceCustomContentView sourceCustomContentView.alpha = sourceCustomContentViewAlpha diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index d616d145ac..2e66217687 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -484,7 +484,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case 61: self = .copyProtectionRequest(hasExpired: decoder.decodeBoolForKey("hasExpired", orElse: false), previousValue: decoder.decodeBoolForKey("previousValue", orElse: false), newValue: decoder.decodeBoolForKey("newValue", orElse: false)) case 62: - self = .managedBotCreated(botId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(decoder.decodeInt64ForKey("botId", orElse: 0)))) + self = .managedBotCreated(botId: PeerId(decoder.decodeInt64ForKey("botId", orElse: 0))) case 63: self = .pollOptionAppended(decoder.decodeObjectForKey("option", decoder: { TelegramMediaPollOption(decoder: $0) }) as! TelegramMediaPollOption) default: diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index a82bc46787..8a14bf06a1 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -389,7 +389,8 @@ public final class ButtonComponent: Component { public let allowActionWhenDisabled: Bool public let displaysProgress: Bool public let action: () -> Void - + public let longPressAction: (() -> Void)? + public init( background: Background, content: AnyComponentWithIdentity, @@ -399,7 +400,8 @@ public final class ButtonComponent: Component { tintWhenDisabled: Bool = true, allowActionWhenDisabled: Bool = false, displaysProgress: Bool = false, - action: @escaping () -> Void + action: @escaping () -> Void, + longPressAction: (() -> Void)? = nil ) { self.background = background self.content = content @@ -410,6 +412,7 @@ public final class ButtonComponent: Component { self.allowActionWhenDisabled = allowActionWhenDisabled self.displaysProgress = displaysProgress self.action = action + self.longPressAction = longPressAction } public static func ==(lhs: ButtonComponent, rhs: ButtonComponent) -> Bool { @@ -437,6 +440,9 @@ public final class ButtonComponent: Component { if lhs.displaysProgress != rhs.displaysProgress { return false } + if (lhs.longPressAction == nil) != (rhs.longPressAction == nil) { + return false + } return true } @@ -462,6 +468,7 @@ public final class ButtonComponent: Component { private var contentItem: ContentItem? private var activityIndicator: ActivityIndicator? + private var longPressGesture: UILongPressGestureRecognizer? override init(frame: CGRect) { self.containerView = UIView() @@ -479,7 +486,12 @@ public final class ButtonComponent: Component { self.addSubview(self.button) self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - + + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressed(_:))) + longPressGesture.isEnabled = false + self.longPressGesture = longPressGesture + self.button.addGestureRecognizer(longPressGesture) + self.button.highligthedChanged = { [weak self] highlighted in if let self, let component = self.component, component.isEnabled { switch component.background.style { @@ -530,6 +542,13 @@ public final class ButtonComponent: Component { } component.action() } + + @objc private func longPressed(_ gesture: UILongPressGestureRecognizer) { + guard gesture.state == .began, let component = self.component else { + return + } + component.longPressAction?() + } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return super.hitTest(point, with: event) @@ -538,6 +557,8 @@ public final class ButtonComponent: Component { func update(component: ButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.componentState = state + + self.longPressGesture?.isEnabled = component.longPressAction != nil self.button.isEnabled = (component.isEnabled || component.allowActionWhenDisabled) && !component.displaysProgress diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index e5e174cb3e..aedd7a31fa 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -765,7 +765,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, - sourceSendButton: node, + sourceSendButton: node.view, textInputView: textInputNode.textView, emojiViewProvider: textInputPanelNode.emojiViewProvider, completion: { diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/BUILD b/submodules/TelegramUI/Components/TextProcessingScreen/BUILD index 81acdd605e..ff2ce1a75b 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/BUILD +++ b/submodules/TelegramUI/Components/TextProcessingScreen/BUILD @@ -40,6 +40,7 @@ swift_library( "//submodules/TelegramNotices", "//submodules/Markdown", "//submodules/TelegramUIPreferences", + "//submodules/ChatSendMessageActionUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index 7818a2c4d9..43090c295f 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -23,6 +23,7 @@ import ToastComponent import TelegramNotices import Markdown import TelegramUIPreferences +import ChatSendMessageActionUI final class TextProcessingContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -98,7 +99,7 @@ final class TextProcessingContentComponent: Component { private var currentContent: (mode: Mode, view: ComponentView)? - private var currentMode: Mode = .translate + private var currentMode: Mode = .stylize override init(frame: CGRect) { self.currentContentBackground = UIImageView() @@ -229,6 +230,7 @@ final class TextProcessingContentComponent: Component { } if self.currentMode != .translate { self.currentMode = .translate + self.saveState() self.externalStatesUpdated() } if !self.isUpdating { @@ -250,6 +252,7 @@ final class TextProcessingContentComponent: Component { } if self.currentMode != .stylize { self.currentMode = .stylize + self.saveState() let _ = ApplicationSpecificNotice.incrementAITextProcessingStyleSelection(accountManager: component.context.sharedContext.accountManager).startStandalone() self.externalStatesUpdated() } @@ -272,6 +275,7 @@ final class TextProcessingContentComponent: Component { } if self.currentMode != .fix { self.currentMode = .fix + self.saveState() self.externalStatesUpdated() } if !self.isUpdating { @@ -591,6 +595,126 @@ private final class TextProcessingSheetComponent: Component { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func displayLongPressSendMenu(sourceSendButton: UIView) { + Task { @MainActor [weak self, weak sourceSendButton] in + guard let self, let sourceSendButton, let component = self.component, case let .edit(peerId, _, _) = component.mode, let peerId else { + return + } + guard let controller = self.environment?.controller() else { + return + } + let previousSupportedOrientations = controller.supportedOrientations + + let availableMessageEffects = await (component.context.availableMessageEffects |> take(1)).get() + let hasPremium = await (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + }).get() + + let peerStatus = await (component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Presence(id: peerId) + )).get() + guard let peer = await (component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + )).get() else { + return + } + + let initialData = await ChatSendMessageContextScreen.initialData(context: component.context, currentMessageEffectId: nil).get() + + var sendWhenOnlineAvailable = false + if let peerStatus, case let .present(until) = peerStatus.status { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if currentTime > until { + sendWhenOnlineAvailable = true + } + } + if peerId.namespace == Namespaces.Peer.CloudUser && peerId.id._internalGetInt64Value() == 777000 { + sendWhenOnlineAvailable = false + } + + let messageActionsController = makeChatSendMessageActionSheetController( + initialData: initialData, + context: component.context, + updatedPresentationData: nil, + peerId: peerId, + params: .sendMessage(SendMessageActionSheetControllerParams.SendMessage( + isScheduledMessages: false, + mediaPreview: nil, + mediaCaptionIsAbove: nil, + messageEffect: (nil, { [weak self] updatedEffect in + guard let self else { + return + } + let _ = self + let _ = updatedEffect + }), + attachment: false, + canSendWhenOnline: sendWhenOnlineAvailable, + forwardMessageIds: [], + canMakePaidContent: false, + currentPrice: nil, + hasTimers: false, + sendPaidMessageStars: nil, + isMonoforum: peer._asPeer().isMonoForum + )), + hasEntityKeyboard: false, + gesture: nil, + sourceSendButton: sourceSendButton, + textInputView: UITextView(), + emojiViewProvider: nil, + completion: { [weak self] in + guard let self else { + return + } + self.environment?.controller()?.supportedOrientations = previousSupportedOrientations + }, + sendMessage: { [weak self] mode, parameters in + guard let self else { + return + } + /*switch mode { + case .generic: + selfController.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init)) + case .silently: + selfController.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init)) + case .whenOnline: + selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + }) + selfController.openScheduledMessages() + } + }*/ + let _ = self + }, + schedule: { [weak self] params in + guard let self else { + return + } + let _ = self + //selfController.controllerInteraction?.scheduleCurrentMessage(params) + }, editPrice: { _ in + }, openPremiumPaywall: { [weak self] c in + guard let self else { + return + } + self.environment?.controller()?.push(c) + }, + reactionItems: nil, + availableMessageEffects: availableMessageEffects, + isPremium: hasPremium + ) + controller.present(messageActionsController, in: .window(.root)) + } + } func update(component: TextProcessingSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component @@ -768,7 +892,13 @@ private final class TextProcessingSheetComponent: Component { dismiss(true) } } - } + }, + longPressSendAction: performSendAction != nil ? { [weak self] sourceView in + guard let self else { + return + } + self.displayLongPressSendMenu(sourceSendButton: sourceView) + } : nil ) ), backgroundColor: .color(theme.list.blocksBackgroundColor), @@ -1121,19 +1251,22 @@ private final class ActionButtonsComponent: Component { let actionButtonShowsIncreaseLimit: Bool let action: (() -> Void)? let sendAction: (() -> Void)? + let longPressSendAction: ((UIView) -> Void)? init( theme: PresentationTheme, actionTitle: String, actionButtonShowsIncreaseLimit: Bool, action: (() -> Void)?, - sendAction: (() -> Void)? + sendAction: (() -> Void)?, + longPressSendAction: ((UIView) -> Void)? ) { self.theme = theme self.actionTitle = actionTitle self.actionButtonShowsIncreaseLimit = actionButtonShowsIncreaseLimit self.action = action self.sendAction = sendAction + self.longPressSendAction = longPressSendAction } static func ==(lhs: ActionButtonsComponent, rhs: ActionButtonsComponent) -> Bool { @@ -1152,6 +1285,9 @@ private final class ActionButtonsComponent: Component { if (lhs.sendAction == nil) != (rhs.sendAction == nil) { return false } + if (lhs.longPressSendAction == nil) != (rhs.longPressSendAction == nil) { + return false + } return true } @@ -1251,6 +1387,15 @@ private final class ActionButtonsComponent: Component { return } component.sendAction?() + }, + longPressAction: component.longPressSendAction == nil ? nil : { [weak self] in + guard let self else { + return + } + guard let sourceView = self.sendButton.view else { + return + } + component.longPressSendAction?(sourceView) } )), environment: {}, diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index 996bfe69ae..b16df87b7e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -146,7 +146,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, - sourceSendButton: node, + sourceSendButton: node.view, textInputView: textInputView, emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider, wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, @@ -242,7 +242,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, - sourceSendButton: node, + sourceSendButton: node.view, textInputView: textInputView, emojiViewProvider: selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider, wallpaperBackgroundNode: selfController.chatDisplayNode.backgroundNode, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 2eb8d1f569..e692c90f36 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -4462,7 +4462,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } let effectiveInputText: NSAttributedString - if effectivePresentationInterfaceState.interfaceState.editMessage != nil && effectivePresentationInterfaceState.interfaceState.postSuggestionState != nil { + if effectivePresentationInterfaceState.interfaceState.editMessage != nil { effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.effectiveInputState.inputText) } else { effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText) diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 73e486adf8..9277a6a1fb 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -258,7 +258,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou if queryItem.name == "url" { url = value } else if queryItem.name == "text" { - text = value + text = value.replacingOccurrences(of: "+", with: " ") } else if queryItem.name == "to" && peerName != "share" { to = value } @@ -601,7 +601,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou for queryItem in components.queryItems ?? [] { if let value = queryItem.value { if queryItem.name == "name" { - title = value + title = value.replacingOccurrences(of: "+", with: " ") } } } From c9e07daedafe774091cecb5ae4936ef179423f4c Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 24 Mar 2026 00:26:50 +0800 Subject: [PATCH 3/4] Temp --- ...ChatSendMessageActionSheetController.swift | 2 +- .../ChatSendMessageContextScreen.swift | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index 1dfac949d0..fcb8304aab 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -80,7 +80,7 @@ public func makeChatSendMessageActionSheetController( hasEntityKeyboard: Bool, gesture: ContextGesture?, sourceSendButton: UIView, - textInputView: UITextView, + textInputView: UITextView?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode? = nil, completion: @escaping () -> Void, diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index db2bc1f4be..78a402669f 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -61,7 +61,7 @@ final class ChatSendMessageContextScreenComponent: Component { let hasEntityKeyboard: Bool let gesture: ContextGesture? let sourceSendButton: UIView - let textInputView: UITextView + let textInputView: UITextView? let emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? let wallpaperBackgroundNode: WallpaperBackgroundNode? let completion: () -> Void @@ -82,7 +82,7 @@ final class ChatSendMessageContextScreenComponent: Component { hasEntityKeyboard: Bool, gesture: ContextGesture?, sourceSendButton: UIView, - textInputView: UITextView, + textInputView: UITextView?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode?, completion: @escaping () -> Void, @@ -389,7 +389,7 @@ final class ChatSendMessageContextScreenComponent: Component { var isMessageVisible: Bool = mediaPreview != nil let textString: NSAttributedString - if let attributedText = component.textInputView.attributedText { + if let attributedText = component.textInputView?.attributedText { textString = attributedText if textString.length != 0 { isMessageVisible = true @@ -731,7 +731,12 @@ final class ChatSendMessageContextScreenComponent: Component { wallpaperBackgroundNode.alpha = 0.0 } - let localSourceTextInputViewFrame = convertFrame(component.textInputView.bounds, from: component.textInputView, to: self) + let localSourceTextInputViewFrame: CGRect + if let textInputView = component.textInputView { + localSourceTextInputViewFrame = convertFrame(textInputView.bounds, from: textInputView, to: self) + } else { + localSourceTextInputViewFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton, to: self) + } let sourceMessageTextInsets = UIEdgeInsets(top: 7.0, left: 12.0, bottom: 6.0, right: 20.0) let sourceBackgroundSize = CGSize(width: localSourceTextInputViewFrame.width + 32.0, height: localSourceTextInputViewFrame.height + 4.0) @@ -1337,14 +1342,14 @@ final class ChatSendMessageContextScreenComponent: Component { return } if mediaPreview == nil { - component.textInputView.isHidden = true + component.textInputView?.isHidden = true } component.sourceSendButton.isHidden = true }) } } else { if mediaPreview == nil { - component.textInputView.isHidden = true + component.textInputView?.isHidden = true } component.sourceSendButton.isHidden = true } @@ -1356,7 +1361,7 @@ final class ChatSendMessageContextScreenComponent: Component { if self.animateOutToEmpty { if mediaPreview == nil { - component.textInputView.isHidden = false + component.textInputView?.isHidden = false } component.sourceSendButton.isHidden = false @@ -1378,7 +1383,7 @@ final class ChatSendMessageContextScreenComponent: Component { self.performedActionsOnAnimateOut = true if let component = self.component, !self.animateOutToEmpty { if mediaPreview == nil { - component.textInputView.isHidden = false + component.textInputView?.isHidden = false } component.sourceSendButton.isHidden = false } @@ -1433,7 +1438,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha hasEntityKeyboard: Bool, gesture: ContextGesture?, sourceSendButton: UIView, - textInputView: UITextView, + textInputView: UITextView?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, wallpaperBackgroundNode: WallpaperBackgroundNode?, completion: @escaping () -> Void, From f437e0bd04a72d34a483ca888125f33723696444 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 24 Mar 2026 01:39:53 +0800 Subject: [PATCH 4/4] Text editor --- .../ChatSendMessageContextScreen.swift | 42 ++++++--- .../Sources/SendButton.swift | 49 ++++++++++- .../TelegramEngine/Messages/Translate.swift | 1 - .../Sources/TextProcessingScreen.swift | 85 +++++++++++-------- .../TelegramUI/Sources/ChatController.swift | 10 ++- .../Sources/ChatControllerNode.swift | 52 +++++++++++- 6 files changed, 187 insertions(+), 52 deletions(-) diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index 78a402669f..7e783df785 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -435,11 +435,15 @@ final class ChatSendMessageContextScreenComponent: Component { } let sendButtonScale: CGFloat - switch self.presentationAnimationState { - case .initial: - sendButtonScale = 0.75 - default: + if component.sourceSendButton is ContextExtractedContentContainingView { sendButtonScale = 1.0 + } else { + switch self.presentationAnimationState { + case .initial: + sendButtonScale = 0.75 + default: + sendButtonScale = 1.0 + } } var reminders = false @@ -1121,7 +1125,12 @@ final class ChatSendMessageContextScreenComponent: Component { } } - let sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 40.0), height: sourceSendButtonFrame.height) + let sendButtonSize: CGSize + if component.sourceSendButton is ContextExtractedContentContainingView { + sendButtonSize = sourceSendButtonFrame.size + } else { + sendButtonSize = CGSize(width: min(sourceSendButtonFrame.width, 40.0), height: sourceSendButtonFrame.height) + } var readySendButtonFrame = CGRect(origin: CGPoint(x: sourceSendButtonFrame.maxX - sendButtonSize.width, y: sourceSendButtonFrame.minY), size: sendButtonSize) var sourceActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - 8.0 - actionsStackSize.width, y: sourceMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) @@ -1139,23 +1148,36 @@ final class ChatSendMessageContextScreenComponent: Component { } } - var readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) + var readyActionsStackFrame: CGRect + if component.sourceSendButton is ContextExtractedContentContainingView { + readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - 8.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing + 4.0), size: actionsStackSize) + } else { + readyActionsStackFrame = CGRect(origin: CGPoint(x: readySendButtonFrame.minX + 1.0 - actionsStackSize.width, y: readyMessageItemFrame.maxY + messageActionsSpacing), size: actionsStackSize) + } if !isMessageVisible { - readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height - 5.0 + if component.sourceSendButton is ContextExtractedContentContainingView { + readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height + } else { + readyActionsStackFrame.origin.y = readySendButtonFrame.maxY - readyActionsStackFrame.height - 5.0 + } } let bottomOverflow = readyActionsStackFrame.maxY - (availableSize.height - environment.safeInsets.bottom) if bottomOverflow > 0.0 { readyMessageItemFrame.origin.y -= bottomOverflow readyActionsStackFrame.origin.y -= bottomOverflow - readySendButtonFrame.origin.y -= bottomOverflow + if !(component.sourceSendButton is ContextExtractedContentContainingView) { + readySendButtonFrame.origin.y -= bottomOverflow + } } - + let inputCoverOverflow = readyMessageItemFrame.maxY + 7.0 - (availableSize.height - environment.inputHeight) if inputCoverOverflow > 0.0 { readyMessageItemFrame.origin.y -= inputCoverOverflow readyActionsStackFrame.origin.y -= inputCoverOverflow - readySendButtonFrame.origin.y -= inputCoverOverflow + if !(component.sourceSendButton is ContextExtractedContentContainingView) { + readySendButtonFrame.origin.y -= inputCoverOverflow + } } if let mediaPreview { diff --git a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift index d089825eac..f197a475d7 100644 --- a/submodules/ChatSendMessageActionUI/Sources/SendButton.swift +++ b/submodules/ChatSendMessageActionUI/Sources/SendButton.swift @@ -34,7 +34,8 @@ final class SendButton: HighlightTrackingButton { private var previousIsAnimatedIn: Bool? private var sourceCustomContentView: UIView? - + private weak var extractedContainerView: ContextExtractedContentContainingView? + init(kind: Kind) { self.kind = kind @@ -70,6 +71,52 @@ final class SendButton: HighlightTrackingButton { size: CGSize, transition: ComponentTransition ) { + if let extractedContainer = sourceSendButton as? ContextExtractedContentContainingView { + self.containerView.isHidden = true + self.sourceCustomContentView?.isHidden = true + + let contentView = extractedContainer.contentView + + if self.extractedContainerView !== extractedContainer { + self.extractedContainerView = extractedContainer + self.previousIsAnimatedIn = nil + } + + if self.previousIsAnimatedIn != isAnimatedIn { + self.previousIsAnimatedIn = isAnimatedIn + + if isAnimatedIn { + extractedContainer.willUpdateIsExtractedToContextPreview?(true, .animated(duration: 0.3, curve: .spring)) + extractedContainer.isExtractedToContextPreview = true + extractedContainer.isExtractedToContextPreviewUpdated?(true) + + extractedContainer.layer.removeAnimation(forKey: "extractedContentReturn") + + self.addSubview(contentView) + } else { + extractedContainer.willUpdateIsExtractedToContextPreview?(false, .animated(duration: 0.3, curve: .spring)) + extractedContainer.isExtractedToContextPreview = false + extractedContainer.isExtractedToContextPreviewUpdated?(false) + + transition.attachAnimation(view: self, id: "extractedContentReturn", completion: { [weak extractedContainer] _ in + guard let extractedContainer else { + return + } + extractedContainer.addSubview(extractedContainer.contentView) + }) + } + } + + if contentView.superview === self { + transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: size)) + } + + return + } + + self.containerView.isHidden = false + self.extractedContainerView = nil + let innerSize: CGSize if size.height == 40.0 { innerSize = CGSize(width: size.width - 3.0 * 2.0, height: size.height - 3.0 * 2.0) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index 50dc9dfb0d..99a542978c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -509,7 +509,6 @@ func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: let inputText: Api.TextWithEntities = .textWithEntities(Api.TextWithEntities.Cons_textWithEntities(text: text.text, entities: apiEntitiesFromMessageTextEntities(text.entities, associatedPeers: SimpleDictionary()))) return account.network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: inputText, translateToLang: translateToLang, changeTone: changeTone)) - |> delay(0.4, queue: .mainQueue()) |> `catch` { error -> Signal in if error.errorDescription == "AICOMPOSE_FLOOD_PREMIUM" { return .fail(.nonPremiumFlood) diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index 43090c295f..7014c3e746 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -172,7 +172,7 @@ final class TextProcessingContentComponent: Component { guard let component = self.component else { return } - if case let .edit(saveRestoreStateId, _, _) = component.mode, let saveRestoreStateId { + if case let .edit(saveRestoreStateId, _, _, _) = component.mode, let saveRestoreStateId { let mappedMode: Int32 switch self.currentMode { case .translate: @@ -598,12 +598,13 @@ private final class TextProcessingSheetComponent: Component { private func displayLongPressSendMenu(sourceSendButton: UIView) { Task { @MainActor [weak self, weak sourceSendButton] in - guard let self, let sourceSendButton, let component = self.component, case let .edit(peerId, _, _) = component.mode, let peerId else { + guard let self, let sourceSendButton, let component = self.component, case let .edit(_, _, _, sendContextActions) = component.mode, let sendContextActions else { return } guard let controller = self.environment?.controller() else { return } + let peerId = sendContextActions.peerId let previousSupportedOrientations = controller.supportedOrientations let availableMessageEffects = await (component.context.availableMessageEffects |> take(1)).get() @@ -674,33 +675,28 @@ private final class TextProcessingSheetComponent: Component { self.environment?.controller()?.supportedOrientations = previousSupportedOrientations }, sendMessage: { [weak self] mode, parameters in - guard let self else { + guard let self, let result = self.contentExternalState.result else { return } - /*switch mode { - case .generic: - selfController.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init)) - case .silently: - selfController.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init)) - case .whenOnline: - selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak selfController] in - guard let selfController else { - return - } - selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } - }) - selfController.openScheduledMessages() + sendContextActions.send(result, mode, parameters) + let controller = self.environment?.controller + self.animateOut.invoke(Action { _ in + if let controller = controller?() { + controller.dismiss(completion: nil) } - }*/ - let _ = self + }) }, schedule: { [weak self] params in - guard let self else { + guard let self, let result = self.contentExternalState.result else { return } - let _ = self - //selfController.controllerInteraction?.scheduleCurrentMessage(params) + sendContextActions.schedule(result, params) + let controller = self.environment?.controller + self.animateOut.invoke(Action { _ in + if let controller = controller?() { + controller.dismiss(completion: nil) + } + }) }, editPrice: { _ in }, openPremiumPaywall: { [weak self] c in guard let self else { @@ -741,6 +737,7 @@ private final class TextProcessingSheetComponent: Component { let performMainAction: () -> Void var performSendAction: ((TextWithEntities) -> Void)? + var hasLongPressActions = false let isMainActionEnabled: Bool //TODO:localize (for AI: don't remove this line) let actionButtonTitle: String @@ -768,10 +765,11 @@ private final class TextProcessingSheetComponent: Component { } } else { switch component.mode { - case let .edit(_, completion, send): + case let .edit(_, completion, send, sendContextActions): actionButtonTitle = "Apply" performSendAction = send isMainActionEnabled = !self.contentExternalState.isProcessing + hasLongPressActions = sendContextActions != nil performMainAction = { [weak self] in guard let self else { return @@ -893,7 +891,7 @@ private final class TextProcessingSheetComponent: Component { } } }, - longPressSendAction: performSendAction != nil ? { [weak self] sourceView in + longPressSendAction: (performSendAction != nil && hasLongPressActions) ? { [weak self] sourceView in guard let self else { return } @@ -1051,8 +1049,20 @@ private final class TextProcessingSheetComponent: Component { } public class TextProcessingScreen: ViewControllerComponentContainer { + public final class SendContextActions { + public let peerId: EnginePeer.Id + public let send: (TextWithEntities, ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void + public let schedule: (TextWithEntities, ChatSendMessageActionSheetController.SendParameters?) -> Void + + public init(peerId: EnginePeer.Id, send: @escaping (TextWithEntities, ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (TextWithEntities, ChatSendMessageActionSheetController.SendParameters?) -> Void) { + self.peerId = peerId + self.send = send + self.schedule = schedule + } + } + public enum Mode { - case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?) + case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?, sendContextActions: SendContextActions?) case translate(fromLanguage: String?) } @@ -1080,7 +1090,7 @@ public class TextProcessingScreen: ViewControllerComponentContainer { let shouldDisplayStyleNotice = await ApplicationSpecificNotice.getAITextProcessingStyleSelection(accountManager: context.sharedContext.accountManager).get() < 3 var initialEditState: EditState? - if case let .edit(saveRestoreStateId, _, _) = mode, let saveRestoreStateId { + if case let .edit(saveRestoreStateId, _, _, _) = mode, let saveRestoreStateId { initialEditState = await context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.textProcessingEditingState(peerId: saveRestoreStateId)) ).get()?.get(EditState.self) @@ -1294,12 +1304,15 @@ private final class ActionButtonsComponent: Component { final class View: UIView { private let actionButton = ComponentView() private let sendButton = ComponentView() - + private let extractedContainerView = ContextExtractedContentContainingView() + private var component: ActionButtonsComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { super.init(frame: frame) + + self.addSubview(self.extractedContainerView) } required init?(coder: NSCoder) { @@ -1392,10 +1405,7 @@ private final class ActionButtonsComponent: Component { guard let self else { return } - guard let sourceView = self.sendButton.view else { - return - } - component.longPressSendAction?(sourceView) + component.longPressSendAction?(self.extractedContainerView) } )), environment: {}, @@ -1404,13 +1414,16 @@ private final class ActionButtonsComponent: Component { let sendButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sendButtonSize.width, y: 0.0), size: sendButtonSize) if let sendButtonView = self.sendButton.view { if sendButtonView.superview == nil { - self.addSubview(sendButtonView) + self.extractedContainerView.contentView.addSubview(sendButtonView) } - transition.setPosition(view: sendButtonView, position: sendButtonFrame.center) - transition.setBounds(view: sendButtonView, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size)) - transition.setAlpha(view: sendButtonView, alpha: component.sendAction != nil ? 1.0 : 0.0) - transition.setScale(view: sendButtonView, scale: component.sendAction != nil ? 1.0 : 0.001) + sendButtonView.frame = CGRect(origin: CGPoint(), size: sendButtonFrame.size) } + transition.setPosition(view: self.extractedContainerView, position: sendButtonFrame.center) + transition.setBounds(view: self.extractedContainerView, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size)) + transition.setPosition(view: self.extractedContainerView.contentView, position: CGPoint(x: sendButtonFrame.width * 0.5, y: sendButtonFrame.height * 0.5)) + transition.setBounds(view: self.extractedContainerView.contentView, bounds: CGRect(origin: CGPoint(), size: sendButtonFrame.size)) + transition.setAlpha(view: self.extractedContainerView, alpha: component.sendAction != nil ? 1.0 : 0.0) + transition.setScale(view: self.extractedContainerView, scale: component.sendAction != nil ? 1.0 : 0.001) return CGSize(width: availableSize.width, height: actionButtonSize.height) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f7be86bdf3..ae3c8f751d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4077,8 +4077,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G mode: .translate(fromLanguage: language), ignoredTranslationLanguages: translationSettings.ignoredLanguages ?? [], inputText: TextWithEntities(text: text.string, entities: entities ?? []), - copyResult: canCopy ? { text in + copyResult: canCopy ? { [weak self] text in + guard let self else { + return + } storeMessageTextInPasteboard(text.text, entities: text.entities) + + let infoText = self.presentationData.strings.Conversation_TextCopied + self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: infoText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + return true + }), in: .current) } : nil, translateChat: { [weak self] toLang in guard let self else { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index e692c90f36..baacfa7135 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -66,6 +66,7 @@ import GroupCallHeaderPanelComponent import PresentationDataUtils import TextProcessingScreen import Pasteboard +import UndoUI final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -4501,7 +4502,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in return state.updatedInterfaceState { interfaceState in return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))) - } + }.updatedInputMode({ _ in return .text }) }) }, send: { [weak self] text in @@ -4514,16 +4515,61 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } }) self.sendCurrentMessage() + }, + sendContextActions: self.chatLocation.peerId.flatMap { peerId in return TextProcessingScreen.SendContextActions( + peerId: peerId, + send: { [weak self] text, mode, parameters in + guard let self, let controller = self.controller else { + return + } + controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedInterfaceState { interfaceState in + return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))) + } + }) + switch mode { + case .generic: + controller.controllerInteraction?.sendCurrentMessage(false, parameters?.effect.flatMap(ChatSendMessageEffect.init)) + case .silently: + controller.controllerInteraction?.sendCurrentMessage(true, parameters?.effect.flatMap(ChatSendMessageEffect.init)) + case .whenOnline: + controller.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp, messageEffect: parameters?.effect.flatMap(ChatSendMessageEffect.init)) { [weak self] in + guard let self, let controller = self.controller else { + return + } + controller.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: controller.presentationInterfaceState.subject != .scheduledMessages, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + }) + controller.openScheduledMessages() + } + } + }, + schedule: { [weak self] text, params in + guard let self, let controller = self.controller else { + return + } + controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedInterfaceState { interfaceState in + return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))) + } + }) + controller.controllerInteraction?.scheduleCurrentMessage(params) + } + ) } ), ignoredTranslationLanguages: translationSettings.ignoredLanguages ?? [], inputText: TextWithEntities(text: inputText.string, entities: entities), copyResult: { [weak self] text in - guard let self else { + guard let self, let controller = self.controller else { return } - let _ = self storeMessageTextInPasteboard(text.text, entities: text.entities) + + let infoText = controller.presentationData.strings.Conversation_TextCopied + controller.present(UndoOverlayController(presentationData: controller.presentationData, content: .copy(text: infoText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + return true + }), in: .current) }, translateChat: nil ))