From befb570fa73353506d67344e752b47480f9af26d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 25 Mar 2022 19:37:47 +0400 Subject: [PATCH] Web app improvements --- .../Sources/AccountContext.swift | 6 +- .../Sources/ChatController.swift | 1 + submodules/AttachmentUI/BUILD | 1 + .../Sources/AttachmentController.swift | 37 +++- .../Sources/AttachmentPanel.swift | 123 +++++++++++-- .../Sources/InstantPageControllerNode.swift | 4 + submodules/PhotoResources/BUILD | 1 + .../Sources/PhotoResources.swift | 39 +++++ .../TelegramEngine/Messages/BotWebView.swift | 14 +- ...OutgoingMessageWithChatContextResult.swift | 4 +- .../Messages/TelegramEngineMessages.swift | 4 + .../BotLogo.imageset/Contents.json | 21 --- .../Bot Payments/BotLogo.imageset/Tmp.png | Bin 4854 -> 0 bytes .../BotPlus.imageset/Contents.json | 12 ++ .../BotPlus.imageset/attachalert_48.pdf | 163 ++++++++++++++++++ .../TelegramUI/Sources/ChatController.swift | 52 +++++- .../Sources/NavigateToChatController.swift | 8 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 30 +++- submodules/TelegramUI/Sources/OpenUrl.swift | 5 + .../Sources/PeerInfo/PeerInfoScreen.swift | 8 +- .../UrlHandling/Sources/UrlHandling.swift | 4 +- submodules/WebUI/BUILD | 1 + .../Sources/WebAppAlertContentNode.swift | 42 ++++- .../WebUI/Sources/WebAppController.swift | 47 ++++- 24 files changed, 546 insertions(+), 81 deletions(-) delete mode 100644 submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/attachalert_48.pdf diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 7670f41087..796dec7fbe 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -117,7 +117,7 @@ public final class AccountWithInfo: Equatable { public enum OpenURLContext { case generic - case chat(updatedPresentationData: (initial: PresentationData, signal: Signal)?) + case chat(peerId: PeerId, updatedPresentationData: (initial: PresentationData, signal: Signal)?) } public struct ChatAvailableMessageActionOptions: OptionSet { @@ -346,6 +346,7 @@ public final class NavigateToChatControllerParams { public let chatLocationContextHolder: Atomic public let subject: ChatControllerSubject? public let botStart: ChatControllerInitialBotStart? + public let attachBotId: PeerId? public let updateTextInputState: ChatTextInputState? public let activateInput: Bool public let keepStack: NavigateToChatKeepStack @@ -364,7 +365,7 @@ public final class NavigateToChatControllerParams { public let changeColors: Bool public let completion: (ChatController) -> Void - public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, completion: @escaping (ChatController) -> Void = { _ in }) { + public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotId: PeerId? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, completion: @escaping (ChatController) -> Void = { _ in }) { self.navigationController = navigationController self.chatController = chatController self.chatLocationContextHolder = chatLocationContextHolder @@ -372,6 +373,7 @@ public final class NavigateToChatControllerParams { self.chatLocation = chatLocation self.subject = subject self.botStart = botStart + self.attachBotId = attachBotId self.updateTextInputState = updateTextInputState self.activateInput = activateInput self.keepStack = keepStack diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index f82c3d695e..329e50c030 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -148,6 +148,7 @@ public enum ChatControllerInteractionNavigateToPeer { case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?) case info case withBotStartPayload(ChatControllerInitialBotStart) + case withAttachBot(PeerId) } public struct ChatInterfaceForwardOptionsState: Codable, Equatable { diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 8fd12635d1..f5b342e9ed 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -29,6 +29,7 @@ swift_library( "//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI", "//submodules/ContextUI:ContextUI", "//submodules/ManagedAnimationNode:ManagedAnimationNode", + "//submodules/PhotoResources:PhotoResources", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 69384f5683..52de061600 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -17,7 +17,7 @@ public enum AttachmentButtonType: Equatable { case location case contact case poll - case app(PeerId, String, TelegramMediaFile?) + case app(PeerId, String, TelegramMediaFile) } public protocol AttachmentContainable: ViewController { @@ -206,9 +206,9 @@ public class AttachmentController: ViewController { } } - self.panel.selectionChanged = { [weak self] type, ascending in + self.panel.selectionChanged = { [weak self] type in if let strongSelf = self { - return strongSelf.switchToController(type, ascending) + return strongSelf.switchToController(type) } else { return false } @@ -268,7 +268,7 @@ public class AttachmentController: ViewController { self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - let _ = self.switchToController(.gallery, false) + let _ = self.switchToController(.gallery) } private func updateSelectionCount(_ count: Int) { @@ -293,7 +293,26 @@ public class AttachmentController: ViewController { } } - func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) -> Bool { + func switchTo(_ type: AttachmentButtonType) { + guard let buttons = self.controller?.buttons else { + return + } + if case let .app(botId, _, _) = type { + let index = buttons.firstIndex(where: { + if case let .app(otherBotId, _, _) = $0, otherBotId == botId { + return true + } else { + return false + } + }) + if let index = index { + self.panel.updateSelectedIndex(index) + let _ = self.switchToController(buttons[index], animated: false) + } + } + } + + func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool { guard self.currentType != type else { if self.animating { return false @@ -338,13 +357,13 @@ public class AttachmentController: ViewController { let previousController = strongSelf.currentControllers.last strongSelf.currentControllers = [controller] - if previousType != nil { + if previousType != nil && animated { strongSelf.animateSwitchTransition(controller, previousController: previousController) } if let layout = strongSelf.validLayout { strongSelf.switchingController = true - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate) strongSelf.switchingController = false } } @@ -613,6 +632,10 @@ public class AttachmentController: ViewController { self.displayNodeDidLoad() } + public func switchTo(_ type: AttachmentButtonType) { + (self.displayNode as! Node).switchTo(type) + } + public func _dismiss() { super.dismiss(animated: false, completion: {}) } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 0eedc58c0c..e7de9e8d56 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -12,11 +12,102 @@ import AttachmentTextInputPanelNode import ChatPresentationInterfaceState import ChatSendMessageActionUI import ChatTextLinkEditUI +import PhotoResources private let buttonSize = CGSize(width: 88.0, height: 49.0) private let iconSize = CGSize(width: 30.0, height: 30.0) private let sideInset: CGFloat = 0.0 +private final class IconComponent: Component { + public let account: Account + public let name: String + public let file: TelegramMediaFile? + public let tintColor: UIColor? + + public init(account: Account, name: String, file: TelegramMediaFile?, tintColor: UIColor?) { + self.account = account + self.name = name + self.file = file + self.tintColor = tintColor + } + + public static func ==(lhs: IconComponent, rhs: IconComponent) -> Bool { + if lhs.account !== rhs.account { + return false + } + if lhs.name != rhs.name { + return false + } + if lhs.file?.fileId != rhs.file?.fileId { + return false + } + if lhs.tintColor != rhs.tintColor { + return false + } + return false + } + + public final class View: UIImageView { + private var component: IconComponent? + private var disposable: Disposable? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable?.dispose() + } + + func update(component: IconComponent, availableSize: CGSize, transition: Transition) -> CGSize { + if self.component?.name != component.name || self.component?.file?.fileId != component.file?.fileId || self.component?.tintColor != component.tintColor { + if let file = component.file { + let previousName = self.component?.name ?? "" + if !previousName.isEmpty { + self.image = nil + } + + _ = freeMediaFileInteractiveFetched(account: component.account, fileReference: .standalone(media: file)).start() + self.disposable = (svgIconImageFile(account: component.account, fileReference: .standalone(media: file), fetched: true) + |> runOn(Queue.concurrentDefaultQueue()) + |> deliverOnMainQueue).start(next: { [weak self] transform in + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: availableSize, boundingSize: availableSize, intrinsicInsets: UIEdgeInsets()) + let drawingContext = transform(arguments) + let image = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) + if let tintColor = component.tintColor { + self?.image = generateTintedImage(image: image, color: tintColor, backgroundColor: nil) + } else { + self?.image = image + } + }) + } else { + if let tintColor = component.tintColor { + self.image = generateTintedImage(image: UIImage(bundleImageName: component.name), color: tintColor, backgroundColor: nil) + } else { + self.image = UIImage(bundleImageName: component.name) + } + } + } + self.component = component + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + + private final class AttachButtonComponent: CombinedComponent { let context: AccountContext let type: AttachmentButtonType @@ -61,13 +152,14 @@ private final class AttachButtonComponent: CombinedComponent { } static var body: Body { - let icon = Child(Image.self) + let icon = Child(IconComponent.self) let title = Child(Text.self) let button = Child(Rectangle.self) return { context in let name: String - let imageName: String? + let imageName: String + var imageFile: TelegramMediaFile? let component = context.component let strings = component.strings @@ -88,20 +180,23 @@ private final class AttachButtonComponent: CombinedComponent { case .poll: name = strings.Attachment_Poll imageName = "Chat/Attach Menu/Poll" - case let .app(_, appName, _): + case let .app(_, appName, appIcon): name = appName - imageName = "Chat List/Tabs/IconSettings" + imageName = "" + imageFile = appIcon } - let image = imageName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) } let tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor + let iconSize = CGSize(width: 30.0, height: 30.0) let icon = icon.update( - component: Image( - image: image, + component: IconComponent( + account: component.context.account, + name: imageName, + file: imageFile, tintColor: tintColor ), - availableSize: CGSize(width: 30.0, height: 30.0), + availableSize: iconSize, transition: context.transition ) @@ -128,7 +223,7 @@ private final class AttachButtonComponent: CombinedComponent { let topInset: CGFloat = 4.0 + UIScreenPixel let spacing: CGFloat = 15.0 + UIScreenPixel - let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - icon.size.width) / 2.0), y: topInset), size: icon.size) + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - icon.size.width) / 2.0), y: topInset), size: iconSize) let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - title.size.width) / 2.0), y: iconFrame.midY + spacing), size: title.size) context.add(title @@ -174,7 +269,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private var validLayout: ContainerViewLayout? private var scrollLayout: (width: CGFloat, contentSize: CGSize)? - var selectionChanged: (AttachmentButtonType, Bool) -> Bool = { _, _ in return false } + var selectionChanged: (AttachmentButtonType) -> Bool = { _ in return false } var beganTextEditing: () -> Void = {} var textUpdated: (NSAttributedString) -> Void = { _ in } var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in } @@ -432,6 +527,11 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { } } + func updateSelectedIndex(_ index: Int) { + self.selectedIndex = index + self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) + } + func updateViews(transition: Transition) { guard let layout = self.validLayout else { return @@ -480,8 +580,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { theme: self.presentationData.theme, action: { [weak self] in if let strongSelf = self { - let ascending = i > strongSelf.selectedIndex - if strongSelf.selectionChanged(type, ascending) { + if strongSelf.selectionChanged(type) { strongSelf.selectedIndex = i strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) } diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 616c5173f4..eebd61c71b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -1248,6 +1248,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: botStart, keepStack: .always)) } + case let .withAttachBot(botId): + if let navigationController = strongSelf.getNavigationController() { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotId: botId)) + } case .info: let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in diff --git a/submodules/PhotoResources/BUILD b/submodules/PhotoResources/BUILD index 5b84550a66..7e743e47c7 100644 --- a/submodules/PhotoResources/BUILD +++ b/submodules/PhotoResources/BUILD @@ -24,6 +24,7 @@ swift_library( "//submodules/WebPBinding:WebPBinding", "//submodules/AppBundle:AppBundle", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", + "//submodules/Svg:Svg", ], visibility = [ "//visibility:public", diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 144fe79123..f9231de7af 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -17,6 +17,7 @@ import TinyThumbnail import ImageTransparency import AppBundle import MusicAlbumArtResources +import Svg private enum ResourceFileData { case data(Data) @@ -2282,6 +2283,44 @@ public func instantPageImageFile(account: Account, fileReference: FileMediaRefer } } +public func svgIconImageFile(account: Account, fileReference: FileMediaReference, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + return chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false, fetched: false) + |> map { value in + let fullSizePath = value._1 + let fullSizeComplete = value._2 + return { arguments in +// assertNotOnMainThread() + let context = DrawingContext(size: arguments.drawingSize, clear: true) + + let drawingRect = arguments.drawingRect + let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + + var fullSizeImage: UIImage? + let imageOrientation: UIImage.Orientation = .up + + if let fullSizePath = fullSizePath { + if fullSizeComplete, let data = try? Data(contentsOf: URL(fileURLWithPath: fullSizePath)) { + fullSizeImage = drawSvgImage(data, CGSize(width: 90.0, height: 90.0), .clear, .black, false) + } + } + + let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) + + context.withFlippedContext { c in + if let fullSizeImage = fullSizeImage?.cgImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect) + } + } + + addCorners(context, arguments: arguments) + + return context + } + } +} + private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], immediateThumbnailData: Data?, autoFetchFullSize: Bool = false, attemptSynchronously: Bool = false) -> Signal, NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift index 7f00f8d269..5f7d75c54e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift @@ -6,7 +6,7 @@ import MtProtoKit public enum RequestWebViewResult { case webViewResult(queryId: Int64, url: String) - case requestConfirmation + case requestConfirmation(botIcon: TelegramMediaFile) } public enum RequestWebViewError { @@ -37,8 +37,8 @@ func _internal_requestWebView(postbox: Postbox, network: Network, peerId: PeerId } |> mapToSignal { result -> Signal in switch result { - case let .webViewResultConfirmationRequired(_, users): - return postbox.transaction { transaction -> RequestWebViewResult in + case let .webViewResultConfirmationRequired(bot, users): + return postbox.transaction { transaction -> Signal in var peers: [Peer] = [] for user in users { let telegramUser = TelegramUser(user: user) @@ -47,9 +47,15 @@ func _internal_requestWebView(postbox: Postbox, network: Network, peerId: PeerId updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in return updated }) - return .requestConfirmation + + if case let .attachMenuBot(_, attachMenuIcon) = bot, let icon = telegramMediaFileFromApiDocument(attachMenuIcon) { + return .single(.requestConfirmation(botIcon: icon)) + } else { + return .complete() + } } |> castError(RequestWebViewError.self) + |> switchToLatest case let .webViewResultUrl(queryId, url): return .single(.webViewResult(queryId: queryId, url: url)) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index 49a6885152..f9ebfee534 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -3,14 +3,14 @@ import Postbox import SwiftSignalKit func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> Bool { - guard let message = outgoingMessageWithChatContextResult(to: peerId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else { + guard let message = _internal_outgoingMessageWithChatContextResult(to: peerId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else { return false } let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start() return true } -private func outgoingMessageWithChatContextResult(to peerId: PeerId, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { +func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { var attributes: [MessageAttribute] = [] attributes.append(OutgoingChatContextResultMessageAttribute(queryId: result.queryId, id: result.id, hideVia: hideVia)) if !hideVia { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 6e7ca00545..21ff8ac888 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -163,6 +163,10 @@ public extension TelegramEngine { public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> Bool { return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) } + + public func outgoingMessageWithChatContextResult(to peerId: PeerId, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { + return _internal_outgoingMessageWithChatContextResult(to: peerId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) + } public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal { return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) diff --git a/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json deleted file mode 100644 index 96acb63cc3..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Tmp.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png b/submodules/TelegramUI/Images.xcassets/Bot Payments/BotLogo.imageset/Tmp.png deleted file mode 100644 index a74a40092b09c3d65f3e70e5cd31b0fd4d5c4e01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4854 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 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/Contents.json new file mode 100644 index 0000000000..a23467970c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attachalert_48.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/attachalert_48.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/attachalert_48.pdf new file mode 100644 index 0000000000..e1f4c04bd5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/attachalert_48.pdf @@ -0,0 +1,163 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 32.166687 6.746460 cm +0.000000 0.000000 0.000000 scn +18.393993 20.192921 m +18.979780 20.778706 18.979780 21.728455 18.393993 22.314240 c +17.808208 22.900028 16.858459 22.900028 16.272675 22.314240 c +18.393993 20.192921 l +h +27.060661 8.859587 m +27.646448 9.445374 27.646448 10.395121 27.060661 10.980907 c +26.474874 11.566692 25.525127 11.566692 24.939341 10.980907 c +27.060661 8.859587 l +h +21.000002 4.920248 m +19.939342 5.980907 l +21.000002 4.920248 l +h +16.272675 22.314240 m +9.939336 15.980904 l +12.060657 13.859583 l +18.393993 20.192921 l +16.272675 22.314240 l +h +18.060659 7.859585 m +28.727329 18.526257 l +26.606010 20.647575 l +15.939340 9.980906 l +18.060659 7.859585 l +h +16.606005 30.647572 m +5.939345 19.980911 l +8.060666 17.859592 l +18.727325 28.526253 l +16.606005 30.647572 l +h +22.060661 3.859587 m +27.060661 8.859587 l +24.939341 10.980907 l +19.939342 5.980907 l +22.060661 3.859587 l +h +5.939340 3.859587 m +10.391120 -0.592194 17.608883 -0.592194 22.060661 3.859587 c +19.939342 5.980907 l +16.659134 2.700701 11.340867 2.700699 8.060660 5.980907 c +5.939340 3.859587 l +h +5.939345 19.980911 m +1.487566 15.529133 1.487559 8.311367 5.939340 3.859587 c +8.060660 5.980907 l +4.780454 9.261112 4.780457 14.579384 8.060666 17.859592 c +5.939345 19.980911 l +h +28.727327 30.647573 m +25.380116 33.994785 19.953217 33.994781 16.606005 30.647572 c +18.727325 28.526253 l +20.902964 30.701891 24.430368 30.701891 26.606007 28.526253 c +28.727327 30.647573 l +h +28.727329 18.526257 m +32.074543 21.873466 32.074539 27.300364 28.727327 30.647573 c +26.606007 28.526253 l +28.781647 26.350615 28.781647 22.823214 26.606010 20.647575 c +28.727329 18.526257 l +h +9.939341 7.859587 m +12.181980 5.616947 15.818019 5.616945 18.060659 7.859585 c +15.939340 9.980906 l +14.868273 8.909840 13.131728 8.909840 12.060660 9.980907 c +9.939341 7.859587 l +h +9.939336 15.980904 m +7.696694 13.738260 7.696702 10.102224 9.939341 7.859587 c +12.060660 9.980907 l +10.989592 11.051975 10.989591 12.788517 12.060657 13.859583 c +9.939336 15.980904 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 5.500000 14.500000 cm +0.000000 0.000000 0.000000 scn +11.000000 17.500000 m +11.000000 18.328426 10.328427 19.000000 9.500000 19.000000 c +8.671573 19.000000 8.000000 18.328426 8.000000 17.500000 c +8.000000 11.000000 l +1.500000 11.000000 l +0.671573 11.000000 0.000000 10.328427 0.000000 9.500000 c +0.000000 8.671573 0.671573 8.000000 1.500000 8.000000 c +8.000000 8.000000 l +8.000000 1.500000 l +8.000000 0.671574 8.671573 0.000000 9.500000 0.000000 c +10.328427 0.000000 11.000000 0.671574 11.000000 1.500000 c +11.000000 8.000000 l +17.500000 8.000000 l +18.328426 8.000000 19.000000 8.671573 19.000000 9.500000 c +19.000000 10.328427 18.328426 11.000000 17.500000 11.000000 c +11.000000 11.000000 l +11.000000 17.500000 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2838 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 72.000000 48.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002928 00000 n +0000002951 00000 n +0000003124 00000 n +0000003198 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3257 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 20da27c1a1..e9bade6c32 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -219,6 +219,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G public let chatLocation: ChatLocation public let subject: ChatControllerSubject? private let botStart: ChatControllerInitialBotStart? + private var attachBotId: PeerId? private let peerDisposable = MetaDisposable() private let titleDisposable = MetaDisposable() @@ -499,7 +500,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var inviteRequestsContext: PeerInvitationImportersContext? private var inviteRequestsDisposable = MetaDisposable() - public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = []) { + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotId: PeerId? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = []) { let _ = ChatControllerCount.modify { value in return value + 1 } @@ -509,6 +510,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatLocationContextHolder = chatLocationContextHolder self.subject = subject self.botStart = botStart + self.attachBotId = attachBotId self.peekData = peekData self.currentChatListFilter = chatListFilter self.chatNavigationStack = chatNavigationStack @@ -3348,7 +3350,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: url, themeParams: nil) + strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: url, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme)) |> afterDisposed { updateProgress() }) @@ -9114,6 +9116,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { self.chatDisplayNode.historyNode.preloadPages = true } + + if let attachBotId = self.attachBotId { + self.attachBotId = nil + self.presentAttachmentBot(botId: attachBotId) + } } override public func viewWillDisappear(_ animated: Bool) { @@ -10455,7 +10462,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - private func presentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) { + public func presentAttachmentBot(botId: PeerId) { + self.presentAttachmentMenu(editMediaOptions: nil, editMediaReference: nil, botId: botId) + } + + private func presentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?, botId: PeerId? = nil) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } @@ -10493,6 +10504,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let presentationData = self.presentationData + var switchToBotImpl: ((AttachmentButtonType) -> Void)? + var switchToBotId = botId let buttons: Signal<[AttachmentButtonType], NoError> if let _ = peer as? TelegramUser { buttons = .single(availableButtons) @@ -10506,7 +10519,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return buttons } - ) + ) |> afterNext { buttons in + if let botId = switchToBotId, let button = buttons.first(where: { + if case let .app(otherBotId, _,_) = $0, botId == otherBotId { + return true + } else { + return false + } + }) { + Queue.mainQueue().justDispatch { + switchToBotImpl?(button) + } + switchToBotId = nil + } + } } else { buttons = .single(availableButtons) } @@ -10518,6 +10544,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let currentLocationController = Atomic(value: nil) let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: self.chatLocation, buttons: buttons) + switchToBotImpl = { [weak attachmentController] button in + attachmentController?.switchTo(button) + } attachmentController.requestController = { [weak self, weak attachmentController] type, completion in guard let strongSelf = self else { return @@ -13783,6 +13812,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedBotStartPayload(botStart.payload) }) + case .withAttachBot: + self.presentAttachmentMenu(editMediaOptions: nil, editMediaReference: nil) default: break } @@ -13837,6 +13868,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } case let .withBotStartPayload(botStart): self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId), botStart: botStart)) + case let .withAttachBot(botId): + if let navigationController = self.effectiveNavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peerId), attachBotId: botId)) + } } } } else { @@ -14261,7 +14296,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?) { - self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(updatedPresentationData: self.updatedPresentationData), navigationController: self.effectiveNavigationController, openPeer: { [weak self] peerId, navigation in + guard let peerId = self.chatLocation.peerId else { + return + } + self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: peerId, updatedPresentationData: self.updatedPresentationData), navigationController: self.effectiveNavigationController, openPeer: { [weak self] peerId, navigation in guard let strongSelf = self else { return } @@ -14294,6 +14332,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let navigationController = strongSelf.effectiveNavigationController { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload)) } + case let .withAttachBot(botId): + if let navigationController = strongSelf.effectiveNavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotId: botId)) + } default: break } diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 3374284a07..e1d6232dec 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -61,6 +61,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam return state.updatedBotStartPayload(botStart.payload) }) } + if let botId = params.attachBotId { + controller.presentAttachmentBot(botId: botId) + } found = true break } @@ -76,8 +79,11 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam return state.updatedBotStartPayload(botStart.payload) }) } + if let botId = params.attachBotId { + controller.presentAttachmentBot(botId: botId) + } } else { - controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack) + controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotId: params.attachBotId, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack) } controller.purposefulAction = params.purposefulAction if let search = params.activateMessageSearch { diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 6541927c7f..2b9f753686 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -45,7 +45,7 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { let updatedPresentationData: (initial: PresentationData, signal: Signal)? - if case let .chat(maybeUpdatedPresentationData) = urlContext { + if case let .chat(_, maybeUpdatedPresentationData) = urlContext { updatedPresentationData = maybeUpdatedPresentationData } else { updatedPresentationData = nil @@ -560,14 +560,28 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) |> deliverOnMainQueue).start(next: { peer in - if let peer = peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canBeAddedToAttachMenu) { - let controller = addWebAppToAttachmentController(sharedContext: context.sharedContext, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { - let _ = context.engine.messages.addBotToAttachMenu(peerId: peerId).start() - }) - present(controller, nil) - } else { - presentError(presentationData.strings.Login_UnknownError) + guard let peer = peer else { + return } + let _ = (context.engine.messages.requestWebView(peerId: peer.id, botId: peer.id, url: nil, themeParams: nil) + |> deliverOnMainQueue).start(next: { result in + if case let .requestConfirmation(botIcon) = result { + if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canBeAddedToAttachMenu) { + let controller = addWebAppToAttachmentController(context: context, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peerIcon: botIcon, completion: { + let _ = context.engine.messages.addBotToAttachMenu(peerId: peerId).start() + + Queue.mainQueue().after(1.0, { + if let navigationController = navigationController, case let .chat(chatPeerId, _) = urlContext { + let _ = context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: chatPeerId), attachBotId: peer.id, useExisting: true)) + } + }) + }) + present(controller, nil) + } else { + presentError(presentationData.strings.Login_UnknownError) + } + } + }) }) } }) diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index e42d0e4930..6a779cd231 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -208,6 +208,11 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if let navigationController = navigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), botStart: payload)) } + case let .withAttachBot(botId): + context.sharedContext.applicationBindings.dismissNativeController() + if let navigationController = navigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), attachBotId: botId)) + } default: break } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 0e7f8b6b9a..db805931b8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3286,7 +3286,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, openPeer: { [weak self] peerId, navigation in + self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, openPeer: { [weak self] peerId, navigation in guard let strongSelf = self else { return } @@ -3305,6 +3305,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate })) case let .withBotStartPayload(startPayload): strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload)) + case let .withAttachBot(botId): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotId: botId)) default: break } @@ -3381,6 +3383,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate if let navigationController = self.controller?.navigationController as? NavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peerId), botStart: startPayload)) } + case let .withAttachBot(botId): + if let navigationController = self.controller?.navigationController as? NavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peerId), attachBotId: botId)) + } } } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 1639c6f13f..6955a6ad7d 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -444,8 +444,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(botPeer?._asPeer()) } |> mapToSignal { botPeer -> Signal in - if let _ = botPeer { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + if let botPeer = botPeer { + return .single(.peer(peer.id, .withAttachBot(botPeer.id))) } else { return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) } diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index 259d81455d..0680b997cc 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/AttachmentUI:AttachmentUI", "//submodules/CounterContollerTitleView:CounterContollerTitleView", "//submodules/HexColor:HexColor", + "//submodules/PhotoResources:PhotoResources", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppAlertContentNode.swift b/submodules/WebUI/Sources/WebAppAlertContentNode.swift index 351e2eb370..028a8bfe82 100644 --- a/submodules/WebUI/Sources/WebAppAlertContentNode.swift +++ b/submodules/WebUI/Sources/WebAppAlertContentNode.swift @@ -9,12 +9,15 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle +import PhotoResources private final class WebAppAlertContentNode: AlertContentNode { private let strings: PresentationStrings private let peerName: String + private let peerIcon: TelegramMediaFile private let textNode: ASTextNode + private let appIconNode: ASImageNode private let iconNode: ASImageNode private let actionNodesSeparator: ASDisplayNode @@ -23,17 +26,24 @@ private final class WebAppAlertContentNode: AlertContentNode { private var validLayout: CGSize? + private var iconDisposable: Disposable? + override var dismissOnOutsideTap: Bool { return self.isUserInteractionEnabled } - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peerName: String, actions: [TextAlertAction]) { + init(account: Account, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peerName: String, peerIcon: TelegramMediaFile, actions: [TextAlertAction]) { self.strings = strings self.peerName = peerName - + self.peerIcon = peerIcon + self.textNode = ASTextNode() self.textNode.maximumNumberOfLines = 0 - + + self.appIconNode = ASImageNode() + self.appIconNode.displaysAsynchronously = false + self.appIconNode.displayWithoutProcessing = true + self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true @@ -58,6 +68,7 @@ private final class WebAppAlertContentNode: AlertContentNode { super.init() self.addSubnode(self.textNode) + self.addSubnode(self.appIconNode) self.addSubnode(self.iconNode) self.addSubnode(self.actionNodesSeparator) @@ -71,11 +82,24 @@ private final class WebAppAlertContentNode: AlertContentNode { } self.updateTheme(theme) + + self.iconDisposable = (svgIconImageFile(account: account, fileReference: .standalone(media: peerIcon)) + |> deliverOnMainQueue).start(next: { [weak self] transform in + if let strongSelf = self { + let availableSize = CGSize(width: 48.0, height: 48.0) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: availableSize, boundingSize: availableSize, intrinsicInsets: UIEdgeInsets()) + let drawingContext = transform(arguments) + let image = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) + strongSelf.appIconNode.image = generateTintedImage(image: image, color: theme.accentColor, backgroundColor: nil) + } + }) } override func updateTheme(_ theme: AlertControllerTheme) { self.textNode.attributedText = NSAttributedString(string: strings.WebApp_AddToAttachmentText(self.peerName).string, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Bot Payments/BotLogo"), color: theme.accentColor) + + self.appIconNode.image = generateTintedImage(image: self.appIconNode.image, color: theme.accentColor) + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/BotPlus"), color: theme.accentColor) self.actionNodesSeparator.backgroundColor = theme.separatorColor for actionNode in self.actionNodes { @@ -189,7 +213,9 @@ private final class WebAppAlertContentNode: AlertContentNode { nodeIndex += 1 } - iconFrame.origin.x = floorToScreenPixels((resultSize.width - iconFrame.width) / 2.0) + iconFrame.origin.x = floorToScreenPixels((resultSize.width - iconFrame.width) / 2.0) + 19.0 + + transition.updateFrame(node: self.appIconNode, frame: CGRect(x: iconFrame.minX - 50.0, y: iconFrame.minY + 3.0, width: 42.0, height: 42.0)) transition.updateFrame(node: self.iconNode, frame: iconFrame) textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0) @@ -199,8 +225,8 @@ private final class WebAppAlertContentNode: AlertContentNode { } } -public func addWebAppToAttachmentController(sharedContext: SharedAccountContext, peerName: String, completion: @escaping () -> Void) -> AlertController { - let presentationData = sharedContext.currentPresentationData.with { $0 } +public func addWebAppToAttachmentController(context: AccountContext, peerName: String, peerIcon: TelegramMediaFile, completion: @escaping () -> Void) -> AlertController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let theme = presentationData.theme let strings = presentationData.strings @@ -214,7 +240,7 @@ public func addWebAppToAttachmentController(sharedContext: SharedAccountContext, completion() })] - contentNode = WebAppAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peerName: peerName, actions: actions) + contentNode = WebAppAlertContentNode(account: context.account, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peerName: peerName, peerIcon: peerIcon, actions: actions) let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) dismissImpl = { [weak controller] animated in diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 6f9623aec6..d577b8793f 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -28,7 +28,7 @@ private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler { } } -private func generateThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] { +public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] { var backgroundColor = presentationTheme.list.plainBackgroundColor.rgb if backgroundColor == 0x000000 { backgroundColor = presentationTheme.list.itemBlocksBackgroundColor.rgb @@ -67,8 +67,12 @@ public final class WebAppController: ViewController, AttachmentContainable { super.init() - self.backgroundColor = .white - + if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 { + self.backgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor + } else { + self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + } + let configuration = WKWebViewConfiguration() let userController = WKUserContentController() @@ -105,6 +109,9 @@ public final class WebAppController: ViewController, AttachmentContainable { } let webView = WKWebView(frame: CGRect(), configuration: configuration) + webView.isOpaque = false + webView.backgroundColor = .clear + if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { webView.allowsLinkPreview = false } @@ -115,9 +122,7 @@ public final class WebAppController: ViewController, AttachmentContainable { return point.x > 30.0 } webView.allowsBackForwardNavigationGestures = false - webView.scrollView.delegate = self - self.view.addSubview(webView) self.webView = webView if let url = url, let queryId = queryId { @@ -126,7 +131,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.load(URLRequest(url: parsedUrl)) } } else { - let _ = (context.engine.messages.requestWebView(peerId: peerId, botId: botId, url: url, themeParams: generateThemeParams(presentationData.theme)) + let _ = (context.engine.messages.requestWebView(peerId: peerId, botId: botId, url: url, themeParams: generateWebAppThemeParams(presentationData.theme)) |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return @@ -144,6 +149,26 @@ public final class WebAppController: ViewController, AttachmentContainable { } } + override func didLoad() { + super.didLoad() + + guard let webView = self.webView else { + return + } + self.view.addSubview(webView) + + if #available(iOS 11.0, *) { + let webScrollView = webView.subviews.compactMap { $0 as? UIScrollView }.first + Queue.mainQueue().after(0.1, { + let contentView = webScrollView?.subviews.first(where: { $0.interactions.count > 1 }) + guard let dragInteraction = (contentView?.interactions.compactMap { $0 as? UIDragInteraction }.first) else { + return + } + contentView?.removeInteraction(dragInteraction) + }) + } + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentOffset = scrollView.contentOffset.y self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) @@ -151,7 +176,7 @@ public final class WebAppController: ViewController, AttachmentContainable { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { if let webView = self.webView { - webView.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom))) + webView.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight - layout.intrinsicInsets.bottom))) } } @@ -194,7 +219,13 @@ public final class WebAppController: ViewController, AttachmentContainable { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - let themeParams = generateThemeParams(presentationData.theme) + if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 { + self.backgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor + } else { + self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + } + + let themeParams = generateWebAppThemeParams(presentationData.theme) var themeParamsString = "{" for (key, value) in themeParams { if let value = value as? Int32 {