diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 102cede554..50e448568e 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -905,7 +905,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } } else { let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in - + let _ = tempFile }) self.present(controller, nil) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 6316a8399f..8ba02042c6 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1309,7 +1309,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-83926371] = { return Api.messages.MyStickers.parse_myStickers($0) } dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) } dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) } - dict[1636301421] = { return Api.messages.PreparedInlineMessage.parse_preparedInlineMessage($0) } + dict[-11046771] = { return Api.messages.PreparedInlineMessage.parse_preparedInlineMessage($0) } dict[-963811691] = { return Api.messages.QuickReplies.parse_quickReplies($0) } dict[1603398491] = { return Api.messages.QuickReplies.parse_quickRepliesNotModified($0) } dict[-352454890] = { return Api.messages.Reactions.parse_reactions($0) } diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index cf310b87b3..e5040b566e 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -866,13 +866,13 @@ public extension Api.messages { } public extension Api.messages { enum PreparedInlineMessage: TypeConstructorDescription { - case preparedInlineMessage(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], users: [Api.User]) + case preparedInlineMessage(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], cacheTime: Int32, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .preparedInlineMessage(let queryId, let result, let peerTypes, let users): + case .preparedInlineMessage(let queryId, let result, let peerTypes, let cacheTime, let users): if boxed { - buffer.appendInt32(1636301421) + buffer.appendInt32(-11046771) } serializeInt64(queryId, buffer: buffer, boxed: false) result.serialize(buffer, true) @@ -881,6 +881,7 @@ public extension Api.messages { for item in peerTypes { item.serialize(buffer, true) } + serializeInt32(cacheTime, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { @@ -892,8 +893,8 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .preparedInlineMessage(let queryId, let result, let peerTypes, let users): - return ("preparedInlineMessage", [("queryId", queryId as Any), ("result", result as Any), ("peerTypes", peerTypes as Any), ("users", users as Any)]) + case .preparedInlineMessage(let queryId, let result, let peerTypes, let cacheTime, let users): + return ("preparedInlineMessage", [("queryId", queryId as Any), ("result", result as Any), ("peerTypes", peerTypes as Any), ("cacheTime", cacheTime as Any), ("users", users as Any)]) } } @@ -908,16 +909,19 @@ public extension Api.messages { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) } - var _4: [Api.User]? + var _4: Int32? + _4 = reader.readInt32() + var _5: [Api.User]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, users: _4!) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, cacheTime: _4!, users: _5!) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift index 4d49aaa5fa..36021a7eee 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift @@ -3,19 +3,23 @@ import Postbox import TelegramApi extension ReplyMarkupButtonAction.PeerTypes { - init?(apiType: Api.InlineQueryPeerType) { - switch apiType { - case .inlineQueryPeerTypePM: - self = .users - case .inlineQueryPeerTypeBotPM: - self = .bots - case .inlineQueryPeerTypeBroadcast: - self = .channels - case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup: - self = .groups - case .inlineQueryPeerTypeSameBotPM: - return nil + init(apiType: [Api.InlineQueryPeerType]) { + var rawValue: Int32 = 0 + for type in apiType { + switch type { + case .inlineQueryPeerTypePM: + rawValue |= ReplyMarkupButtonAction.PeerTypes.users.rawValue + case .inlineQueryPeerTypeBotPM: + rawValue |= ReplyMarkupButtonAction.PeerTypes.bots.rawValue + case .inlineQueryPeerTypeBroadcast: + rawValue |= ReplyMarkupButtonAction.PeerTypes.channels.rawValue + case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup: + rawValue |= ReplyMarkupButtonAction.PeerTypes.groups.rawValue + case .inlineQueryPeerTypeSameBotPM: + break + } } + self.init(rawValue: rawValue) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 53c6ef5955..8c91f86d7c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -2162,6 +2162,34 @@ public extension TelegramEngine.EngineData.Item { } } + public struct BotAppSettings: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = Optional + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + if let cachedData = view.cachedPeerData as? CachedUserData { + return cachedData.botInfo?.appSettings + } else { + return nil + } + } + } + public struct BotCommands: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = Optional<[BotCommand]> diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PreparedInlineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PreparedInlineMessages.swift index d4ce48ffd0..014525d9bf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PreparedInlineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PreparedInlineMessages.swift @@ -5,9 +5,10 @@ import TelegramApi import MtProtoKit public struct PreparedInlineMessage: Equatable { - let queryId: Int64 - let result: ChatContextResult - let peerTypes: [ReplyMarkupButtonAction.PeerTypes] + public let botId: EnginePeer.Id + public let queryId: Int64 + public let result: ChatContextResult + public let peerTypes: ReplyMarkupButtonAction.PeerTypes } func _internal_getPreparedInlineMessage(account: Account, botId: EnginePeer.Id, id: String) -> Signal { @@ -29,10 +30,15 @@ func _internal_getPreparedInlineMessage(account: Account, botId: EnginePeer.Id, } return account.postbox.transaction { transaction -> PreparedInlineMessage? in switch result { - case let .preparedInlineMessage(queryId, result, peerTypes, users): + case let .preparedInlineMessage(queryId, result, apiPeerTypes, cacheTime, users): updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: users)) - - return PreparedInlineMessage(queryId: queryId, result: ChatContextResult(apiResult: result, queryId: queryId), peerTypes: peerTypes.compactMap { ReplyMarkupButtonAction.PeerTypes(apiType: $0) }) + let _ = cacheTime + return PreparedInlineMessage( + botId: botId, + queryId: queryId, + result: ChatContextResult(apiResult: result, queryId: queryId), + peerTypes: ReplyMarkupButtonAction.PeerTypes(apiType: apiPeerTypes) + ) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 955f29e570..10f0ff60d1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -7,6 +7,7 @@ public enum EngineOutgoingMessageContent { case text(String, [MessageTextEntity]) case file(FileMediaReference) case contextResult(ChatContextResultCollection, ChatContextResult) + case preparedInlineMessage(PreparedInlineMessage) } public final class StoryPreloadInfo { @@ -249,7 +250,9 @@ public extension TelegramEngine { scheduleTime: Int32? = nil ) -> Signal<[MessageId?], NoError> { var message: EnqueueMessage? - if case let .contextResult(results, result) = content { + if case let .preparedInlineMessage(preparedInlineMessage) = content { + message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: preparedInlineMessage.botId, result: preparedInlineMessage.result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil) + } else if case let .contextResult(results, result) = content { message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil) } else { var attributes: [MessageAttribute] = [] diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 6bbd6909d7..2edecb817a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1610,6 +1610,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + if isPreview, let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramUser, peer.firstName == nil { + hasAvatar = false + effectiveAuthor = nil + } + var isInstantVideo = false if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser { for media in item.content.firstMessage.media { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 5a7fc07df5..05552d7fca 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1555,7 +1555,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese |> deliverOnMainQueue).startStandalone() })) - if data.webAppPermissions?.location?.isRequested == true { + if data.webAppPermissions?.location?.isRequested == true || data.webAppPermissions?.location?.isAllowed == true { items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 32, text: "Geolocation", value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value)) @@ -5459,6 +5459,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + var appSettings: BotAppSettings? + if let settings = self.data?.cachedData as? CachedUserData { + appSettings = settings.botInfo?.appSettings + } + let presentationData = self.presentationData let proceed: (Bool) -> Void = { [weak self] installed in guard let self else { @@ -5466,7 +5471,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let context = self.context let peerId = self.peerId - let params = WebAppParameters(source: .settings, peerId: self.context.account.peerId, botId: bot.peer.id, botName: bot.peer.compactDisplayTitle, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: bot.flags.contains(.hasSettings), fullSize: true) + let params = WebAppParameters(source: .settings, peerId: self.context.account.peerId, botId: bot.peer.id, botName: bot.peer.compactDisplayTitle, botVerified: bot.peer.isVerified, botAddress: bot.peer.addressName ?? "", url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: bot.flags.contains(.hasSettings), fullSize: true, appSettings: appSettings) var openUrlImpl: ((String, Bool, Bool, @escaping () -> Void) -> Void)? var presentImpl: ((ViewController, Any?) -> Void)? diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index cf8220130b..d723f0a018 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -94,187 +94,72 @@ func openWebAppImpl( } } - - let openWebView = { [weak parentController] in - guard let parentController else { - return - } - if source == .menu { - if let parentController = parentController as? ChatControllerImpl { - parentController.updateChatPresentationInterfaceState(interactive: false) { state in - return state.updatedForceInputCommandsHidden(true) - } + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotAppSettings(id: botPeer.id)) + |> deliverOnMainQueue).start(next: { appSettings in + let openWebView = { [weak parentController] in + guard let parentController else { + return } - - if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer { - for controller in minimizedContainer.controllers { - if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == botPeer.id && mainController.source == .menu { - navigationController.maximizeViewController(controller, animated: true) - return - } - } - } - - var fullSize = false - var isFullscreen = false - if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl { - if url.contains("mode=fullscreen") { - isFullscreen = true - fullSize = true - } else { - fullSize = !url.contains("mode=compact") - } - } - - var presentImpl: ((ViewController, Any?) -> Void)? - let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen) - let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in - ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in - presentImpl?(c, a) - }, commit: commit) - }, requestSwitchInline: { [weak parentController] query, chatTypes, completion in - ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botPeer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) - }, getInputContainerNode: { [weak parentController] in - if let parentController = parentController as? ChatControllerImpl, let layout = parentController.validLayout, case .compact = layout.metrics.widthClass { - return (parentController.chatDisplayNode.getWindowInputAccessoryHeight(), parentController.chatDisplayNode.inputPanelContainerNode, { - return parentController.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil) - }) - } else { - return nil - } - }, completion: { [weak parentController] in - if let parentController = parentController as? ChatControllerImpl { - parentController.chatDisplayNode.historyNode.scrollToEndOfHistory() - } - }, willDismiss: { [weak parentController] in - if let parentController = parentController as? ChatControllerImpl { - parentController.interfaceInteraction?.updateShowWebView { _ in - return false - } - } - }, didDismiss: { [weak parentController] in + if source == .menu { if let parentController = parentController as? ChatControllerImpl { parentController.updateChatPresentationInterfaceState(interactive: false) { state in - return state.updatedForceInputCommandsHidden(false) + return state.updatedForceInputCommandsHidden(true) } } - }, getNavigationController: { [weak parentController] in - var navigationController: NavigationController? - if let parentController = parentController as? ChatControllerImpl { - navigationController = parentController.effectiveNavigationController - } - return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) - }) - controller.navigationPresentation = .flatModal - parentController.push(controller) - - presentImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) - } - } else if simple { - var isInline = false - var botId = botPeer.id - var botName = botName - var botAddress = "" - var botVerified = botPeer.isVerified - if case let .inline(bot) = source { - isInline = true - botId = bot.id - botName = bot.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - botAddress = bot.addressName ?? "" - botVerified = bot.isVerified - } - - let messageActionCallbackDisposable: MetaDisposable - if let parentController = parentController as? ChatControllerImpl { - messageActionCallbackDisposable = parentController.messageActionCallbackDisposable - } else { - messageActionCallbackDisposable = MetaDisposable() - } - - let webViewSignal: Signal - let webViewSource: RequestSimpleWebViewSource - if let payload { - webViewSource = .inline(startParam: payload) - } else { - webViewSource = .generic - } - if url.isEmpty { - webViewSignal = context.engine.messages.requestMainWebView(peerId: chatPeer?.id ?? botId, botId: botId, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme)) - } else { - webViewSignal = context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme)) - } - - messageActionCallbackDisposable.set(((webViewSignal - |> afterDisposed { - updateProgress() - }) - |> deliverOnMainQueue).start(next: { [weak parentController] result in - guard let parentController else { - return - } - var presentImpl: ((ViewController, Any?) -> Void)? - let source: WebAppParameters.Source - if isInline { - source = .inline - } else { - source = url.isEmpty ? .generic : .simple - } - let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen)) - let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in - ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in - presentImpl?(c, a) - }, commit: commit) - }, requestSwitchInline: { [weak parentController] query, chatTypes, completion in - ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) - }, getNavigationController: { [weak parentController] in - var navigationController: NavigationController? - if let parentController = parentController as? ChatControllerImpl { - navigationController = parentController.effectiveNavigationController - } - return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) - }) - controller.navigationPresentation = .flatModal - if let parentController = parentController as? ChatControllerImpl { - parentController.currentWebAppController = controller - } - parentController.push(controller) - presentImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) + if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer { + for controller in minimizedContainer.controllers { + if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == botPeer.id && mainController.source == .menu { + navigationController.maximizeViewController(controller, animated: true) + return + } + } } - }, error: { [weak parentController] error in - if let parentController { - parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]), in: .window(.root)) - } - })) - } else { - let messageActionCallbackDisposable: MetaDisposable - if let parentController = parentController as? ChatControllerImpl { - messageActionCallbackDisposable = parentController.messageActionCallbackDisposable - } else { - messageActionCallbackDisposable = MetaDisposable() - } - - messageActionCallbackDisposable.set(((context.engine.messages.requestWebView(peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: threadId) - |> afterDisposed { - updateProgress() - }) - |> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in - guard let parentController else { - return + + var fullSize = false + var isFullscreen = false + if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl { + if url.contains("mode=fullscreen") { + isFullscreen = true + fullSize = true + } else { + fullSize = !url.contains("mode=compact") + } } + var presentImpl: ((ViewController, Any?) -> Void)? - let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen)) + let params = WebAppParameters(source: .menu, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: fullSize, isFullscreen: isFullscreen, appSettings: appSettings) + let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in presentImpl?(c, a) }, commit: commit) + }, requestSwitchInline: { [weak parentController] query, chatTypes, completion in + ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botPeer.id, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) + }, getInputContainerNode: { [weak parentController] in + if let parentController = parentController as? ChatControllerImpl, let layout = parentController.validLayout, case .compact = layout.metrics.widthClass { + return (parentController.chatDisplayNode.getWindowInputAccessoryHeight(), parentController.chatDisplayNode.inputPanelContainerNode, { + return parentController.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil) + }) + } else { + return nil + } }, completion: { [weak parentController] in if let parentController = parentController as? ChatControllerImpl { parentController.chatDisplayNode.historyNode.scrollToEndOfHistory() } + }, willDismiss: { [weak parentController] in + if let parentController = parentController as? ChatControllerImpl { + parentController.interfaceInteraction?.updateShowWebView { _ in + return false + } + } + }, didDismiss: { [weak parentController] in + if let parentController = parentController as? ChatControllerImpl { + parentController.updateChatPresentationInterfaceState(interactive: false) { state in + return state.updatedForceInputCommandsHidden(false) + } + } }, getNavigationController: { [weak parentController] in var navigationController: NavigationController? if let parentController = parentController as? ChatControllerImpl { @@ -283,51 +168,169 @@ func openWebAppImpl( return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) }) controller.navigationPresentation = .flatModal - if let parentController = parentController as? ChatControllerImpl { - parentController.currentWebAppController = controller - } parentController.push(controller) presentImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } - }, error: { [weak parentController] error in - if let parentController { - parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]), in: .window(.root)) + } else if simple { + var isInline = false + var botId = botPeer.id + var botName = botName + var botAddress = "" + var botVerified = botPeer.isVerified + if case let .inline(bot) = source { + isInline = true + botId = bot.id + botName = bot.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + botAddress = bot.addressName ?? "" + botVerified = bot.isVerified } - })) - } - } - - if skipTermsOfService { - openWebView() - } else { - var botPeer = botPeer - if case let .inline(bot) = source { - botPeer = bot - } - let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id) - |> deliverOnMainQueue).startStandalone(next: { [weak parentController] value in - guard let parentController else { - return - } - - if value { - openWebView() - } else { - let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() - openWebView() - }, showMore: nil, openTerms: { - if let navigationController = parentController.navigationController as? NavigationController { - context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) - } + + let messageActionCallbackDisposable: MetaDisposable + if let parentController = parentController as? ChatControllerImpl { + messageActionCallbackDisposable = parentController.messageActionCallbackDisposable + } else { + messageActionCallbackDisposable = MetaDisposable() + } + + let webViewSignal: Signal + let webViewSource: RequestSimpleWebViewSource + if let payload { + webViewSource = .inline(startParam: payload) + } else { + webViewSource = .generic + } + if url.isEmpty { + webViewSignal = context.engine.messages.requestMainWebView(peerId: chatPeer?.id ?? botId, botId: botId, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme)) + } else { + webViewSignal = context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: webViewSource, themeParams: generateWebAppThemeParams(presentationData.theme)) + } + + messageActionCallbackDisposable.set(((webViewSignal + |> afterDisposed { + updateProgress() }) - parentController.present(controller, in: .window(.root)) + |> deliverOnMainQueue).start(next: { [weak parentController] result in + guard let parentController else { + return + } + var presentImpl: ((ViewController, Any?) -> Void)? + let source: WebAppParameters.Source + if isInline { + source = .inline + } else { + source = url.isEmpty ? .generic : .simple + } + let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) + let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in + ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in + presentImpl?(c, a) + }, commit: commit) + }, requestSwitchInline: { [weak parentController] query, chatTypes, completion in + ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) + }, getNavigationController: { [weak parentController] in + var navigationController: NavigationController? + if let parentController = parentController as? ChatControllerImpl { + navigationController = parentController.effectiveNavigationController + } + return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) + }) + controller.navigationPresentation = .flatModal + if let parentController = parentController as? ChatControllerImpl { + parentController.currentWebAppController = controller + } + parentController.push(controller) + + presentImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } + }, error: { [weak parentController] error in + if let parentController { + parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]), in: .window(.root)) + } + })) + } else { + let messageActionCallbackDisposable: MetaDisposable + if let parentController = parentController as? ChatControllerImpl { + messageActionCallbackDisposable = parentController.messageActionCallbackDisposable + } else { + messageActionCallbackDisposable = MetaDisposable() + } + + messageActionCallbackDisposable.set(((context.engine.messages.requestWebView(peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: threadId) + |> afterDisposed { + updateProgress() + }) + |> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in + guard let parentController else { + return + } + var presentImpl: ((ViewController, Any?) -> Void)? + let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) + let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in + ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in + presentImpl?(c, a) + }, commit: commit) + }, completion: { [weak parentController] in + if let parentController = parentController as? ChatControllerImpl { + parentController.chatDisplayNode.historyNode.scrollToEndOfHistory() + } + }, getNavigationController: { [weak parentController] in + var navigationController: NavigationController? + if let parentController = parentController as? ChatControllerImpl { + navigationController = parentController.effectiveNavigationController + } + return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) + }) + controller.navigationPresentation = .flatModal + if let parentController = parentController as? ChatControllerImpl { + parentController.currentWebAppController = controller + } + parentController.push(controller) + + presentImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } + }, error: { [weak parentController] error in + if let parentController { + parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]), in: .window(.root)) + } + })) } - }) - } + } + + if skipTermsOfService { + openWebView() + } else { + var botPeer = botPeer + if case let .inline(bot) = source { + botPeer = bot + } + let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id) + |> deliverOnMainQueue).startStandalone(next: { [weak parentController] value in + guard let parentController else { + return + } + + if value { + openWebView() + } else { + let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() + openWebView() + }, showMore: nil, openTerms: { + if let navigationController = parentController.navigationController as? NavigationController { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) + } + }) + parentController.present(controller, in: .window(.root)) + } + }) + } + }) } public extension ChatControllerImpl { @@ -496,7 +499,7 @@ public extension ChatControllerImpl { return } let context = strongSelf.context - let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen)) + let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, botAddress: botPeer.addressName ?? "", url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: nil) var presentImpl: ((ViewController, Any?) -> Void)? let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, forceUpdate, commit in ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 62e682f00f..8f63fbc7f0 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -13,6 +13,7 @@ import TelegramNotices import PresentationDataUtils import TelegramCallsUI import AttachmentUI +import WebUI func updateChatPresentationInterfaceStateImpl( selfController: ChatControllerImpl, @@ -591,7 +592,7 @@ func updateChatPresentationInterfaceStateImpl( if selfController.presentationInterfaceState.hasBirthdayToday { selfController.displayBirthdayTooltip() } - + if case .standard(.embedded) = selfController.presentationInterfaceState.mode, let controllerInteraction = selfController.controllerInteraction, let interfaceInteraction = selfController.interfaceInteraction { if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(selfController.presentationInterfaceState, context: selfController.context, currentPanel: selfController.customNavigationPanelNode as? ChatTitleAccessoryPanelNode, controllerInteraction: controllerInteraction, interfaceInteraction: interfaceInteraction, force: true) { selfController.customNavigationPanelNode = titleAccessoryPanelNode as? ChatControllerCustomNavigationPanelNode @@ -600,5 +601,9 @@ func updateChatPresentationInterfaceStateImpl( } } + if let cachedData = selfController.peerView?.cachedData as? CachedUserData, let appSettings = cachedData.botInfo?.appSettings { + let _ = WebAppController.preloadAppPlaceholder(context: selfController.context, appSettings: appSettings).startStandalone() + } + selfController.stateUpdated?(transition) } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index ab8901e8b6..a50802c432 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -613,7 +613,7 @@ extension ChatControllerImpl { payload = botPayload fromAttachMenu = false } - let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: false, isFullscreen: false) + let params = WebAppParameters(source: fromAttachMenu ? .attachMenu : .generic, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, botAddress: bot.peer.addressName ?? "", url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: false, isFullscreen: false) let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageSubject?.messageId, threadId: strongSelf.chatLocation.threadId) controller.openUrl = { [weak self] url, concealed, forceUpdate, commit in diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index 49d110863f..8d92f68ccd 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/AttachmentUI:AttachmentUI", "//submodules/HexColor:HexColor", "//submodules/PhotoResources:PhotoResources", + "//submodules/MediaResources", "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/LegacyComponents:LegacyComponents", "//submodules/UrlHandling:UrlHandling", diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index fb09575cd7..02cd98b2e5 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -15,6 +15,7 @@ import PresentationDataUtils import HexColor import ShimmerEffect import PhotoResources +import MediaResources import LegacyComponents import UrlHandling import MoreButtonNode @@ -33,6 +34,7 @@ import OverlayStatusController import TelegramUIPreferences import CoreMotion import DeviceLocationManager +import LegacyMediaPickerUI private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -60,6 +62,7 @@ public struct WebAppParameters { let botId: PeerId let botName: String let botVerified: Bool + let botAddress: String let url: String? let queryId: Int64? let payload: String? @@ -68,6 +71,7 @@ public struct WebAppParameters { let forceHasSettings: Bool let fullSize: Bool let isFullscreen: Bool + let appSettings: BotAppSettings? public init( source: Source, @@ -75,6 +79,7 @@ public struct WebAppParameters { botId: PeerId, botName: String, botVerified: Bool, + botAddress: String, url: String?, queryId: Int64?, payload: String?, @@ -82,25 +87,24 @@ public struct WebAppParameters { keepAliveSignal: Signal?, forceHasSettings: Bool, fullSize: Bool, - isFullscreen: Bool = false + isFullscreen: Bool = false, + appSettings: BotAppSettings? = nil ) { self.source = source self.peerId = peerId self.botId = botId self.botName = botName self.botVerified = botVerified + self.botAddress = botAddress self.url = url self.queryId = queryId self.payload = payload self.buttonText = buttonText self.keepAliveSignal = keepAliveSignal self.forceHasSettings = forceHasSettings - self.fullSize = fullSize -// #if DEBUG -// self.isFullscreen = true -// #else + self.fullSize = fullSize || isFullscreen self.isFullscreen = isFullscreen -// #endif + self.appSettings = appSettings } } @@ -239,7 +243,21 @@ public final class WebAppController: ViewController, AttachmentContainable { self.placeholderNode = placeholderNode let placeholder: Signal<(FileMediaReference, Bool)?, NoError> - if durgerKingBotIds.contains(controller.botId.id._internalGetInt64Value()) { + if let botAppSettings = controller.botAppSettings { + Queue.mainQueue().justDispatch { + if let backgroundColor = botAppSettings.backgroundColor { + self.appBackgroundColor = UIColor(rgb: UInt32(bitPattern: backgroundColor)) + self.updateBackgroundColor(transition: .immediate) + } + if let headerColor = botAppSettings.headerColor { + self.headerColor = UIColor(rgb: UInt32(bitPattern: headerColor)) + self.updateHeaderBackgroundColor(transition: .immediate) + } + } + } + if let placeholderFile = controller.botAppSettings?.placeholder { + placeholder = .single((.standalone(media: placeholderFile), false)) + } else if durgerKingBotIds.contains(controller.botId.id._internalGetInt64Value()) { placeholder = .single(nil) |> delay(0.05, queue: Queue.mainQueue()) } else { @@ -289,7 +307,7 @@ public final class WebAppController: ViewController, AttachmentContainable { if let fileReference = fileReference { let _ = freeMediaFileInteractiveFetched(account: strongSelf.context.account, userLocation: .other, fileReference: fileReference).start() } - strongSelf.placeholderDisposable.set((svgIconImageFile(account: strongSelf.context.account, fileReference: fileReference, stickToTop: isPlaceholder) + let _ = (svgIconImageFile(account: strongSelf.context.account, fileReference: fileReference, stickToTop: isPlaceholder) |> deliverOnMainQueue).start(next: { [weak self] transform in if let strongSelf = self { let imageSize: CGSize @@ -309,7 +327,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } strongSelf.placeholderNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - })) + }) })) self.iconDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId)) @@ -461,9 +479,24 @@ public final class WebAppController: ViewController, AttachmentContainable { shapes = [.image(image: image, rect: CGRect(origin: CGPoint(), size: image.size))] placeholderSize = image.size } - - let theme = self.presentationData.theme - self.placeholderNode?.update(backgroundColor: .clear, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: placeholderSize, mask: true) + + let foregroundColor: UIColor + let shimmeringColor: UIColor + if let backgroundColor = self.appBackgroundColor { + if backgroundColor.lightness > 0.705 { + foregroundColor = backgroundColor.mixedWith(UIColor(rgb: 0x000000), alpha: 0.05) + shimmeringColor = UIColor.white.withAlphaComponent(0.2) + } else { + foregroundColor = backgroundColor.mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05) + shimmeringColor = UIColor.white.withAlphaComponent(0.4) + } + } else { + let theme = self.presentationData.theme + foregroundColor = theme.list.mediaPlaceholderColor + shimmeringColor = theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4) + } + + self.placeholderNode?.update(backgroundColor: .clear, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, shapes: shapes, horizontal: true, size: placeholderSize, mask: true) return placeholderSize } @@ -1097,8 +1130,8 @@ public final class WebAppController: ViewController, AttachmentContainable { } case "web_app_set_background_color": if let json = json, let colorValue = json["color"] as? String, let color = UIColor(hexString: colorValue) { - let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) - transition.updateBackgroundColor(node: self.backgroundNode, color: color) + self.appBackgroundColor = color + self.updateBackgroundColor(transition: .animated(duration: 0.2, curve: .linear)) } case "web_app_set_header_color": if let json = json { @@ -1403,7 +1436,8 @@ public final class WebAppController: ViewController, AttachmentContainable { } fileprivate var needDismissConfirmation = false - + + fileprivate var appBackgroundColor: UIColor? fileprivate var headerColor: UIColor? fileprivate var headerPrimaryTextColor: UIColor? private var headerColorKey: String? @@ -1415,6 +1449,10 @@ public final class WebAppController: ViewController, AttachmentContainable { } fileprivate let bottomPanelColorPromise = Promise(nil) + private func updateBackgroundColor(transition: ContainedViewLayoutTransition) { + transition.updateBackgroundColor(node: self.backgroundNode, color: self.appBackgroundColor ?? .clear) + } + private func updateHeaderBackgroundColor(transition: ContainedViewLayoutTransition) { guard let controller = self.controller else { return @@ -2143,7 +2181,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self?.webView?.sendEvent(name: "prepared_message_failed", data: "{error: \"MESSAGE_EXPIRED\"}") return } - let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, preparedMessage: preparedMessage, completion: { [weak self] result in + let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, botAddress: controller.botAddress, preparedMessage: preparedMessage, completion: { [weak self] result in guard let self else { return } @@ -2162,6 +2200,7 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let controller = self.controller else { return } + var isMedia = true var title: String? let photoExtensions = [".jpg", ".png", ".gif", ".tiff"] let videoExtensions = [".mp4", ".mov"] @@ -2182,6 +2221,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } if title == nil { title = "Download Document" + isMedia = false } let _ = (FileDownload.getFileSize(url: url) @@ -2201,7 +2241,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self?.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}") }), TextAlertAction(type: .defaultAction, title: "Download", action: { [weak self] in - self?.startDownload(url: url, fileName: fileName, fileSize: fileSize) + self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia) }) ], parseMarkdown: true) controller.present(alertController, in: .window(.root)) @@ -2210,7 +2250,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private var fileDownload: FileDownload? private weak var fileDownloadTooltip: UndoOverlayController? - fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?) { + fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?, isMedia: Bool) { guard let controller = self.controller else { return } @@ -2238,8 +2278,70 @@ public final class WebAppController: ViewController, AttachmentContainable { undoText: "Cancel" ) }, - completion: { resultUrl, _ in - + completion: { [weak self] resultUrl, _ in + if let resultUrl, let self { + if let tooltip = self.fileDownloadTooltip { + tooltip.dismissWithCommitAction() + } + + if isMedia { + let saveToPhotos: (URL, Bool) -> Void = { url, isVideo in + var fileExtension = (resultUrl.absoluteString as NSString).pathExtension + if fileExtension.isEmpty { + fileExtension = "mp4" + } + PHPhotoLibrary.shared().performChanges({ + if isVideo { + PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url) + } else { + if let fileData = try? Data(contentsOf: url) { + PHAssetCreationRequest.forAsset().addResource(with: .photo, data: fileData, options: nil) + } + } + }, completionHandler: { _, error in + }) + } + let isVideo = fileName.lowercased().hasSuffix(".mp4") || fileName.lowercased().hasSuffix(".mov") + saveToPhotos(resultUrl, isVideo) + + let tooltipController = UndoOverlayController( + presentationData: self.presentationData, + content: .actionSucceeded(title: fileName, text: isMedia ? "Saved to Photos" : "Saved to Files", cancel: nil, destructive: false), + elevatedLayout: false, + position: .top, + action: { _ in + return true + } + ) + controller.present(tooltipController, in: .current) + } else { + let tempFile = TempBox.shared.file(path: resultUrl.absoluteString, fileName: fileName) + let url = URL(fileURLWithPath: tempFile.path) + try? FileManager.default.copyItem(at: resultUrl, to: url) + + let pickerController = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { [weak self, weak controller] urls in + guard let self, let controller, !urls.isEmpty else { + return + } + let tooltipController = UndoOverlayController( + presentationData: self.presentationData, + content: .actionSucceeded(title: fileName, text: isMedia ? "Saved to Photos" : "Saved to Files", cancel: nil, destructive: false), + elevatedLayout: false, + position: .top, + action: { _ in + return true + } + ) + controller.present(tooltipController, in: .current) + }) + controller.present(pickerController, in: .window(.root)) +// +// if let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { +// let targetUrl = documentsUrl.appendingPathComponent(fileName) +// try? FileManager.default.copyItem(at: resultUrl, to: targetUrl) +// } + } + } } ) @@ -2264,7 +2366,7 @@ public final class WebAppController: ViewController, AttachmentContainable { return true } ) - controller.present(tooltipController, in: .window(.root)) + controller.present(tooltipController, in: .current) self.fileDownloadTooltip = tooltipController } @@ -2551,6 +2653,8 @@ public final class WebAppController: ViewController, AttachmentContainable { public let botId: PeerId fileprivate let botName: String fileprivate let botVerified: Bool + fileprivate let botAppSettings: BotAppSettings? + fileprivate let botAddress: String private let url: String? private let queryId: Int64? private let payload: String? @@ -2579,6 +2683,8 @@ public final class WebAppController: ViewController, AttachmentContainable { self.botId = params.botId self.botName = params.botName self.botVerified = params.botVerified + self.botAppSettings = params.appSettings + self.botAddress = params.botAddress self.url = params.url self.queryId = params.queryId self.payload = params.payload @@ -2605,6 +2711,7 @@ public final class WebAppController: ViewController, AttachmentContainable { super.init(navigationBarPresentationData: navigationBarPresentationData) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + self.automaticallyControlPresentationContextLayout = false if !self.isFullscreen { self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) @@ -2905,6 +3012,14 @@ public final class WebAppController: ViewController, AttachmentContainable { self.validLayout = layout super.containerLayoutUpdated(layout, transition: transition) + var presentationLayout = layout + if self.isFullscreen { + presentationLayout.intrinsicInsets.top = (presentationLayout.statusBarHeight ?? 0.0) + 36.0 + } else { + presentationLayout.intrinsicInsets.top = 56.0 + } + self.presentationContext.containerLayoutUpdated(presentationLayout, transition: transition) + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } @@ -3015,6 +3130,30 @@ public final class WebAppController: ViewController, AttachmentContainable { }) return imageView } + + public static func preloadAppPlaceholder(context: AccountContext, appSettings: BotAppSettings) -> Signal { + guard let file = appSettings.placeholder else { + return .complete() + } + let path = context.account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) + if !FileManager.default.fileExists(atPath: path) { + let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + let accountResource = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true) + + let fetchedFullSize = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file), reference: .standalone(resource: file.resource)) + let fetchedFullSizeDisposable = fetchedFullSize.start() + let fullSizeDisposable = accountResource.start() + + return ActionDisposable { + fetchedFullSizeDisposable.dispose() + fullSizeDisposable.dispose() + } + } + return accountFullSizeData + |> ignoreValues + } + return .complete() + } } final class WebAppPickerContext: AttachmentMediaPickerContext { diff --git a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift index 18297b55ef..9eaf827044 100644 --- a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift +++ b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift @@ -17,49 +17,22 @@ import ChatMessageItemImpl final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { struct MessageItem: Equatable { static func ==(lhs: MessageItem, rhs: MessageItem) -> Bool { - if lhs.outgoing != rhs.outgoing { - return false - } - if lhs.peerId != rhs.peerId { - return false - } - if lhs.author != rhs.author { - return false - } - if lhs.photo != rhs.photo { - return false - } - if lhs.nameColor != rhs.nameColor { - return false - } - if lhs.backgroundEmojiId != rhs.backgroundEmojiId { - return false - } - if let lhsReply = lhs.reply, let rhsReply = rhs.reply, lhsReply.0 != rhsReply.0 || lhsReply.1 != rhsReply.1 || lhsReply.2 != rhsReply.2 { - return false - } else if (lhs.reply == nil) != (rhs.reply == nil) { - return false - } - if let lhsLinkPreview = lhs.linkPreview, let rhsLinkPreview = rhs.linkPreview, lhsLinkPreview.0 != rhsLinkPreview.0 || lhsLinkPreview.1 != rhsLinkPreview.1 || lhsLinkPreview.2 != rhsLinkPreview.2 { - return false - } else if (lhs.linkPreview == nil) != (rhs.linkPreview == nil) { - return false - } if lhs.text != rhs.text { return false } + if areMediaArraysEqual(lhs.media, rhs.media) { + return false + } + if lhs.botAddress != rhs.botAddress { + return false + } return true } - let outgoing: Bool - let peerId: EnginePeer.Id - let author: String - let photo: [TelegramMediaImageRepresentation] - let nameColor: PeerNameColor - let backgroundEmojiId: Int64? - let reply: (String, String, PeerNameColor)? - let linkPreview: (String, String, String)? let text: String + let entities: TextEntitiesMessageAttribute? + let media: [Media] + let botAddress: String } let context: AccountContext @@ -202,9 +175,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { let currentNodes = self.messageNodes var currentBackgroundNode = self.backgroundNode - - let currentItem = self.item - + return { item, params, neighbors in if currentBackgroundNode == nil { currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false) @@ -214,40 +185,27 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { var insets: UIEdgeInsets let separatorHeight = UIScreenPixel - - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1)) - + var items: [ListViewItem] = [] for messageItem in item.messageItems.reversed() { - let authorPeerId = messageItem.peerId - let replyAuthorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(10)) + let authorPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)) + let botPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) var peers = SimpleDictionary() - var messages = SimpleDictionary() + let messages = SimpleDictionary() - peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: messageItem.author, lastName: "", username: nil, phone: nil, photo: messageItem.photo, botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: messageItem.nameColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil) - - - let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - if let (replyAuthor, text, replyColor) = messageItem.reply { - peers[replyAuthorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: replyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: replyColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil) - - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[replyAuthorPeerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - } - - var media: [Media] = [] - if let (site, title, text) = messageItem.linkPreview, params.width > 320.0 { - media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))) - } + peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil) + peers[botPeerId] = TelegramUser(id: botPeerId, accessHash: nil, firstName: messageItem.botAddress, lastName: "", username: messageItem.botAddress, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil) + let media = messageItem.media var attributes: [MessageAttribute] = [] - if messageItem.reply != nil { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false)) + if let entities = messageItem.entities { + attributes.append(entities) } - attributes.append(InlineBotMessageAttribute(peerId: nil, title: "Test Attach")) + attributes.append(InlineBotMessageAttribute(peerId: botPeerId, title: nil)) - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: authorPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false)) } @@ -311,16 +269,6 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) - if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId || currentItem.theme !== item.theme || currentItem.wallpaper != item.wallpaper { - if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) { - snapshot.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top), size: snapshot.frame.size) - strongSelf.view.addSubview(snapshot) - snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.25, removeOnCompletion: false, completion: { _ in - snapshot.removeFromSuperview() - }) - } - } - strongSelf.messageNodes = nodes var topOffset: CGFloat = 4.0 for node in nodes { diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index 99994bedee..2fefd828cd 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -27,17 +27,20 @@ private final class SheetContent: CombinedComponent { let context: AccountContext let botName: String + let botAddress: String let preparedMessage: PreparedInlineMessage let dismiss: () -> Void init( context: AccountContext, botName: String, + botAddress: String, preparedMessage: PreparedInlineMessage, dismiss: @escaping () -> Void ) { self.context = context self.botName = botName + self.botAddress = botAddress self.preparedMessage = preparedMessage self.dismiss = dismiss } @@ -101,7 +104,7 @@ private final class SheetContent: CombinedComponent { return (TelegramTextAttributes.URL, contents) }) - let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Test Attach mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("\(component.botName) mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural)) let amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), maximumNumberOfLines: 0, @@ -120,17 +123,45 @@ private final class SheetContent: CombinedComponent { } } )) + + var text: String = "" + var entities: TextEntitiesMessageAttribute? + var media: [Media] = [] + + switch component.preparedMessage.result { + case let .internalReference(reference): + switch reference.message { + case let .auto(textValue, entitiesValue, _): + text = textValue + entities = entitiesValue + if let file = reference.file { + media = [file] + } else if let image = reference.image { + media = [image] + } + case let .text(textValue, entitiesValue, disableUrlPreview, previewParameters, _): + text = textValue + entities = entitiesValue + let _ = disableUrlPreview + let _ = previewParameters + case let .contact(contact, _): + media = [contact] + case let .mapLocation(map, _): + media = [map] + case let .invoice(invoice, _): + media = [invoice] + default: + break + } + case let .externalReference(reference): + let _ = reference + } let messageItem = PeerNameColorChatPreviewItem.MessageItem( - outgoing: true, - peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), - author: "", - photo: [], - nameColor: .blue, - backgroundEmojiId: nil, - reply: nil, - linkPreview: nil, - text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + text: text, + entities: entities, + media: media, + botAddress: component.botAddress ) let listItemParams = ListViewItemLayoutParams(width: context.availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true) @@ -197,7 +228,7 @@ private final class SheetContent: CombinedComponent { displaysProgress: false, action: { if let controller = controller() as? WebAppMessagePreviewScreen { - let _ = controller + controller.proceed() } } ), @@ -232,15 +263,18 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent { private let context: AccountContext private let botName: String + private let botAddress: String private let preparedMessage: PreparedInlineMessage init( context: AccountContext, botName: String, + botAddress: String, preparedMessage: PreparedInlineMessage ) { self.context = context self.botName = botName + self.botAddress = botAddress self.preparedMessage = preparedMessage } @@ -265,6 +299,7 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent { content: AnyComponent(SheetContent( context: context.component.context, botName: context.component.botName, + botAddress: context.component.botAddress, preparedMessage: context.component.preparedMessage, dismiss: { animateOut.invoke(Action { _ in @@ -290,12 +325,14 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent { dismiss: { animated in if animated { animateOut.invoke(Action { _ in - if let controller = controller() { + if let controller = controller() as? WebAppMessagePreviewScreen { + controller.completeWithResult(false) controller.dismiss(completion: nil) } }) } else { - if let controller = controller() { + if let controller = controller() as? WebAppMessagePreviewScreen { + controller.completeWithResult(false) controller.dismiss(completion: nil) } } @@ -317,15 +354,18 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent { public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer { private let context: AccountContext + private let preparedMessage: PreparedInlineMessage fileprivate let completion: (Bool) -> Void public init( context: AccountContext, botName: String, + botAddress: String, preparedMessage: PreparedInlineMessage, completion: @escaping (Bool) -> Void ) { self.context = context + self.preparedMessage = preparedMessage self.completion = completion super.init( @@ -333,6 +373,7 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer component: WebAppMessagePreviewSheetComponent( context: context, botName: botName, + botAddress: botAddress, preparedMessage: preparedMessage ), navigationBarAppearance: .none, @@ -346,8 +387,43 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + + fileprivate func complete(peer: EnginePeer) { + self.completeWithResult(true) + self.dismiss() + + let _ = self.context.engine.messages.enqueueOutgoingMessage( + to: peer.id, + replyTo: nil, + storyId: nil, + content: .preparedInlineMessage(preparedMessage) + ).start() + } + + private var completed = false + fileprivate func completeWithResult(_ result: Bool) { + guard !self.completed else { + return + } + self.completion(result) + } + + fileprivate func proceed() { + let requestPeerType = self.preparedMessage.peerTypes.requestPeerTypes + let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: requestPeerType, hasContactSelector: false)) + + controller.peerSelected = { [weak self, weak controller] peer, _ in + guard let self else { + return + } + self.complete(peer: peer) + controller?.dismiss() + } + self.push(controller) + } + public func dismissAnimated() { + self.completeWithResult(false) if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { view.dismissAnimated() }