From 0aac8d47ac3e8a52bebfe263733179371c0cabb6 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Mon, 23 Mar 2026 21:33:34 +0800 Subject: [PATCH] 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