Merge commit 'befb570fa73353506d67344e752b47480f9af26d'

This commit is contained in:
Ali 2022-03-25 21:08:27 +04:00
commit 23bb1a4c5f
49 changed files with 1756 additions and 189 deletions

View File

@ -7414,3 +7414,6 @@ Sorry for the inconvenience.";
"WebApp.AddToAttachmentText" = "%@ asks your permission to be added as an option to your attachments menu so you access it from any chat.";
"WebApp.AddToAttachmentAdd" = "Add";
"WebApp.AddToAttachmentAlreadyAddedError" = "This bot is already added in the attachment menu.";

View File

@ -117,7 +117,7 @@ public final class AccountWithInfo: Equatable {
public enum OpenURLContext {
case generic
case chat(updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?)
case chat(peerId: PeerId, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?)
}
public struct ChatAvailableMessageActionOptions: OptionSet {
@ -188,10 +188,10 @@ public struct ResolvedBotAdminRights: OptionSet {
public static let pinMessages = ResolvedBotAdminRights(rawValue: 128)
public static let promoteMembers = ResolvedBotAdminRights(rawValue: 256)
public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512)
public static let manageChat = ResolvedBotAdminRights(rawValue: 1024)
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 2048)
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024)
public static let manageChat = ResolvedBotAdminRights(rawValue: 2048)
public var chatAdminRights: TelegramChatAdminRightsFlags {
public var chatAdminRights: TelegramChatAdminRightsFlags? {
var flags = TelegramChatAdminRightsFlags()
if self.contains(ResolvedBotAdminRights.changeInfo) {
@ -224,6 +224,11 @@ public struct ResolvedBotAdminRights: OptionSet {
if self.contains(ResolvedBotAdminRights.canBeAnonymous) {
flags.insert(.canBeAnonymous)
}
if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) {
return nil
}
return flags
}
}
@ -253,6 +258,7 @@ public enum ResolvedUrl {
case settings(ResolvedUrlSettingsSection)
case joinVoiceChat(PeerId, String?)
case importStickers
case setAttach(PeerId)
}
public enum NavigateToChatKeepStack {
@ -340,6 +346,7 @@ public final class NavigateToChatControllerParams {
public let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
public let subject: ChatControllerSubject?
public let botStart: ChatControllerInitialBotStart?
public let attachBotId: PeerId?
public let updateTextInputState: ChatTextInputState?
public let activateInput: Bool
public let keepStack: NavigateToChatKeepStack
@ -358,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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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
@ -366,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

View File

@ -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 {

View File

@ -29,6 +29,7 @@ swift_library(
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
"//submodules/ContextUI:ContextUI",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/PhotoResources:PhotoResources",
],
visibility = [
"//visibility:public",

View File

@ -123,6 +123,9 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
return true
}
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UILongPressGestureRecognizer {
return true
}
return false
}

View File

@ -17,7 +17,7 @@ public enum AttachmentButtonType: Equatable {
case location
case contact
case poll
case app(String)
case app(PeerId, String, TelegramMediaFile)
}
public protocol AttachmentContainable: ViewController {
@ -97,7 +97,7 @@ public class AttachmentController: ViewController {
private let context: AccountContext
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private let chatLocation: ChatLocation
private let buttons: [AttachmentButtonType]
private var buttons: [AttachmentButtonType]
public var mediaPickerContext: AttachmentMediaPickerContext? {
get {
@ -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
}
}
@ -564,11 +583,13 @@ public class AttachmentController: ViewController {
completion(nil, nil)
}
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType]) {
private var buttonsDisposable: Disposable?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: Signal<[AttachmentButtonType], NoError>) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.chatLocation = chatLocation
self.buttons = buttons
self.buttons = []
super.init(navigationBarPresentationData: nil)
@ -581,10 +602,21 @@ public class AttachmentController: ViewController {
strongSelf.node.scrollToTop()
}
}
self.buttonsDisposable = (buttons
|> deliverOnMainQueue).start(next: { [weak self] buttons in
if let strongSelf = self {
let previousButtons = strongSelf.buttons
strongSelf.buttons = buttons
if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: !previousButtons.isEmpty ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
}
}
})
}
deinit {
print()
self.buttonsDisposable?.dispose()
}
public required init(coder aDecoder: NSCoder) {
@ -600,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: {})
}
@ -621,9 +657,12 @@ public class AttachmentController: ViewController {
return false
}
private var validLayout: ContainerViewLayout?
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.validLayout = layout
self.node.containerLayoutUpdated(layout, transition: transition)
}
}

View File

@ -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<Empty>, 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)))
}

View File

@ -15,24 +15,33 @@ public struct CounterContollerTitle: Equatable {
}
public final class CounterContollerTitleView: UIView {
private var theme: PresentationTheme
private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
public var title: CounterContollerTitle = CounterContollerTitle(title: "", counter: "") {
didSet {
if self.title != oldValue {
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
self.accessibilityLabel = self.title.title
self.accessibilityValue = self.title.counter
self.setNeedsLayout()
self.update()
}
}
}
public var theme: PresentationTheme {
didSet {
self.update()
}
}
private func update() {
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.title.counter, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
self.accessibilityLabel = self.title.title
self.accessibilityValue = self.title.counter
self.setNeedsLayout()
}
public init(theme: PresentationTheme) {
self.theme = theme

View File

@ -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

View File

@ -24,6 +24,7 @@ swift_library(
"//submodules/WebPBinding:WebPBinding",
"//submodules/AppBundle:AppBundle",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/Svg:Svg",
],
visibility = [
"//visibility:public",

View File

@ -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<Tuple3<Data?, Data?, Bool>, 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 }) {

View File

@ -292,6 +292,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1885586395] = { return Api.Update.parse_updatePendingJoinRequests($0) }
dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) }
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
dict[397910539] = { return Api.Update.parse_updateAttachMenuBots($0) }
dict[1951948721] = { return Api.Update.parse_updateReadFeed($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
@ -323,6 +324,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
dict[-376962181] = { return Api.KeyboardButton.parse_inputKeyboardButtonUserProfile($0) }
dict[814112961] = { return Api.KeyboardButton.parse_keyboardButtonUserProfile($0) }
dict[326529584] = { return Api.KeyboardButton.parse_keyboardButtonWebView($0) }
dict[383348795] = { return Api.ContactStatus.parse_contactStatus($0) }
dict[997004590] = { return Api.users.UserFull.parse_userFull($0) }
dict[1679398724] = { return Api.SecureFile.parse_secureFileEmpty($0) }
@ -391,6 +393,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) }
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
dict[-219353309] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) }
dict[-729926056] = { return Api.AttachMenuBot.parse_attachMenuBot($0) }
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) }
@ -425,6 +428,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[341499403] = { return Api.Contact.parse_contact($0) }
dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) }
dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) }
dict[202659196] = { return Api.WebViewResult.parse_webViewResultUrl($0) }
dict[-1312107643] = { return Api.WebViewResult.parse_webViewResultConfirmationRequired($0) }
dict[1648543603] = { return Api.FileHash.parse_fileHash($0) }
dict[295067450] = { return Api.BotInlineResult.parse_botInlineResult($0) }
dict[400266251] = { return Api.BotInlineResult.parse_botInlineMediaResult($0) }
@ -844,6 +849,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) }
dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) }
dict[1336154098] = { return Api.InputBotInlineResult.parse_inputBotInlineResultGame($0) }
dict[-237467044] = { return Api.AttachMenuBots.parse_attachMenuBotsNotModified($0) }
dict[1011024320] = { return Api.AttachMenuBots.parse_attachMenuBots($0) }
dict[1352683077] = { return Api.account.PrivacyRules.parse_privacyRules($0) }
dict[-123988] = { return Api.PrivacyRule.parse_privacyValueAllowContacts($0) }
dict[1698855810] = { return Api.PrivacyRule.parse_privacyValueAllowAll($0) }
@ -962,6 +969,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[512177195] = { return Api.Document.parse_document($0) }
dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) }
dict[-1493633966] = { return Api.WebAuthorization.parse_webAuthorization($0) }
dict[-1428220517] = { return Api.messages.WebViewResult.parse_webViewResult($0) }
dict[-1052885936] = { return Api.ImportedContact.parse_importedContact($0) }
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
return dict
@ -1223,6 +1231,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.ChatAdminWithInvites:
_1.serialize(buffer, boxed)
case let _1 as Api.AttachMenuBot:
_1.serialize(buffer, boxed)
case let _1 as Api.DialogFilterSuggested:
_1.serialize(buffer, boxed)
case let _1 as Api.auth.PasswordRecovery:
@ -1247,6 +1257,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.help.PassportConfig:
_1.serialize(buffer, boxed)
case let _1 as Api.WebViewResult:
_1.serialize(buffer, boxed)
case let _1 as Api.FileHash:
_1.serialize(buffer, boxed)
case let _1 as Api.BotInlineResult:
@ -1629,6 +1641,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.InputBotInlineResult:
_1.serialize(buffer, boxed)
case let _1 as Api.AttachMenuBots:
_1.serialize(buffer, boxed)
case let _1 as Api.account.PrivacyRules:
_1.serialize(buffer, boxed)
case let _1 as Api.PrivacyRule:
@ -1699,6 +1713,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.WebAuthorization:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.WebViewResult:
_1.serialize(buffer, boxed)
case let _1 as Api.ImportedContact:
_1.serialize(buffer, boxed)
case let _1 as Api.payments.BankCardData:

View File

@ -2702,5 +2702,51 @@ public struct messages {
}
}
public enum WebViewResult: TypeConstructorDescription {
case webViewResult(result: Api.BotInlineResult, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .webViewResult(let result, let users):
if boxed {
buffer.appendInt32(-1428220517)
}
result.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .webViewResult(let result, let users):
return ("webViewResult", [("result", result), ("users", users)])
}
}
public static func parse_webViewResult(_ reader: BufferReader) -> WebViewResult? {
var _1: Api.BotInlineResult?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.BotInlineResult
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.WebViewResult.webViewResult(result: _1!, users: _2!)
}
else {
return nil
}
}
}
}
}

View File

@ -4936,6 +4936,7 @@ public extension Api {
case updatePendingJoinRequests(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64])
case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32)
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
case updateAttachMenuBots
case updateReadFeed(flags: Int32, filterId: Int32, maxPosition: Api.FeedPosition, unreadCount: Int32?, unreadMutedCount: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
@ -5781,6 +5782,12 @@ public extension Api {
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
reactions.serialize(buffer, true)
break
case .updateAttachMenuBots:
if boxed {
buffer.appendInt32(397910539)
}
break
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
if boxed {
@ -5989,6 +5996,8 @@ public extension Api {
return ("updateBotChatInviteRequester", [("peer", peer), ("date", date), ("userId", userId), ("about", about), ("invite", invite), ("qts", qts)])
case .updateMessageReactions(let peer, let msgId, let reactions):
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)])
case .updateAttachMenuBots:
return ("updateAttachMenuBots", [])
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
return ("updateReadFeed", [("flags", flags), ("filterId", filterId), ("maxPosition", maxPosition), ("unreadCount", unreadCount), ("unreadMutedCount", unreadMutedCount)])
}
@ -7722,6 +7731,9 @@ public extension Api {
return nil
}
}
public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? {
return Api.Update.updateAttachMenuBots
}
public static func parse_updateReadFeed(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
@ -8253,6 +8265,7 @@ public extension Api {
case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String)
case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser)
case keyboardButtonUserProfile(text: String, userId: Int64)
case keyboardButtonWebView(text: String, url: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -8351,6 +8364,13 @@ public extension Api {
serializeString(text, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
break
case .keyboardButtonWebView(let text, let url):
if boxed {
buffer.appendInt32(326529584)
}
serializeString(text, buffer: buffer, boxed: false)
serializeString(url, buffer: buffer, boxed: false)
break
}
}
@ -8382,6 +8402,8 @@ public extension Api {
return ("inputKeyboardButtonUserProfile", [("text", text), ("userId", userId)])
case .keyboardButtonUserProfile(let text, let userId):
return ("keyboardButtonUserProfile", [("text", text), ("userId", userId)])
case .keyboardButtonWebView(let text, let url):
return ("keyboardButtonWebView", [("text", text), ("url", url)])
}
}
@ -8585,6 +8607,20 @@ public extension Api {
return nil
}
}
public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? {
var _1: String?
_1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!)
}
else {
return nil
}
}
}
public enum ContactStatus: TypeConstructorDescription {
@ -10366,6 +10402,46 @@ public extension Api {
}
}
}
public enum AttachMenuBot: TypeConstructorDescription {
case attachMenuBot(botId: Int64, attachMenuIcon: Api.Document)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .attachMenuBot(let botId, let attachMenuIcon):
if boxed {
buffer.appendInt32(-729926056)
}
serializeInt64(botId, buffer: buffer, boxed: false)
attachMenuIcon.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .attachMenuBot(let botId, let attachMenuIcon):
return ("attachMenuBot", [("botId", botId), ("attachMenuIcon", attachMenuIcon)])
}
}
public static func parse_attachMenuBot(_ reader: BufferReader) -> AttachMenuBot? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Api.Document?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Document
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.AttachMenuBot.attachMenuBot(botId: _1!, attachMenuIcon: _2!)
}
else {
return nil
}
}
}
public enum DialogFilterSuggested: TypeConstructorDescription {
case dialogFilterSuggested(filter: Api.DialogFilter, description: String)
@ -11276,6 +11352,76 @@ public extension Api {
}
}
}
public enum WebViewResult: TypeConstructorDescription {
case webViewResultUrl(queryId: Int64, url: String)
case webViewResultConfirmationRequired(bot: Api.AttachMenuBot, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .webViewResultUrl(let queryId, let url):
if boxed {
buffer.appendInt32(202659196)
}
serializeInt64(queryId, buffer: buffer, boxed: false)
serializeString(url, buffer: buffer, boxed: false)
break
case .webViewResultConfirmationRequired(let bot, let users):
if boxed {
buffer.appendInt32(-1312107643)
}
bot.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .webViewResultUrl(let queryId, let url):
return ("webViewResultUrl", [("queryId", queryId), ("url", url)])
case .webViewResultConfirmationRequired(let bot, let users):
return ("webViewResultConfirmationRequired", [("bot", bot), ("users", users)])
}
}
public static func parse_webViewResultUrl(_ reader: BufferReader) -> WebViewResult? {
var _1: Int64?
_1 = reader.readInt64()
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.WebViewResult.webViewResultUrl(queryId: _1!, url: _2!)
}
else {
return nil
}
}
public static func parse_webViewResultConfirmationRequired(_ reader: BufferReader) -> WebViewResult? {
var _1: Api.AttachMenuBot?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.AttachMenuBot
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.WebViewResult.webViewResultConfirmationRequired(bot: _1!, users: _2!)
}
else {
return nil
}
}
}
public enum FileHash: TypeConstructorDescription {
case fileHash(offset: Int32, limit: Int32, hash: Buffer)
@ -21600,6 +21746,72 @@ public extension Api {
}
}
}
public enum AttachMenuBots: TypeConstructorDescription {
case attachMenuBotsNotModified
case attachMenuBots(hash: Int64, bots: [Api.AttachMenuBot], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .attachMenuBotsNotModified:
if boxed {
buffer.appendInt32(-237467044)
}
break
case .attachMenuBots(let hash, let bots, let users):
if boxed {
buffer.appendInt32(1011024320)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(bots.count))
for item in bots {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .attachMenuBotsNotModified:
return ("attachMenuBotsNotModified", [])
case .attachMenuBots(let hash, let bots, let users):
return ("attachMenuBots", [("hash", hash), ("bots", bots), ("users", users)])
}
}
public static func parse_attachMenuBotsNotModified(_ reader: BufferReader) -> AttachMenuBots? {
return Api.AttachMenuBots.attachMenuBotsNotModified
}
public static func parse_attachMenuBots(_ reader: BufferReader) -> AttachMenuBots? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.AttachMenuBot]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AttachMenuBot.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.AttachMenuBots.attachMenuBots(hash: _1!, bots: _2!, users: _3!)
}
else {
return nil
}
}
}
public enum PrivacyRule: TypeConstructorDescription {
case privacyValueAllowContacts

View File

@ -4849,6 +4849,84 @@ public extension Api {
return result
})
}
public static func getAttachMenuBots(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.AttachMenuBots>) {
let buffer = Buffer()
buffer.appendInt32(385663691)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getAttachMenuBots", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBots? in
let reader = BufferReader(buffer)
var result: Api.AttachMenuBots?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.AttachMenuBots
}
return result
})
}
public static func toggleBotInAttachMenu(bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(451818415)
bot.serialize(buffer, true)
enabled.serialize(buffer, true)
return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("bot", bot), ("enabled", enabled)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.WebViewResult>) {
let buffer = Buffer()
buffer.appendInt32(-1585704741)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
bot.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {themeParams!.serialize(buffer, true)}
return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", flags), ("peer", peer), ("bot", bot), ("url", url), ("themeParams", themeParams)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in
let reader = BufferReader(buffer)
var result: Api.WebViewResult?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.WebViewResult
}
return result
})
}
public static func setWebViewResult(queryId: Int64, result: Api.InputBotInlineResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-467873507)
serializeInt64(queryId, buffer: buffer, boxed: false)
result.serialize(buffer, true)
return (FunctionDescription(name: "messages.setWebViewResult", parameters: [("queryId", queryId), ("result", result)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func getWebViewResult(peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.WebViewResult>) {
let buffer = Buffer()
buffer.appendInt32(582402580)
peer.serialize(buffer, true)
bot.serialize(buffer, true)
serializeInt64(queryId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getWebViewResult", parameters: [("peer", peer), ("bot", bot), ("queryId", queryId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebViewResult? in
let reader = BufferReader(buffer)
var result: Api.messages.WebViewResult?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.WebViewResult
}
return result
})
}
}
public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {

View File

@ -1091,6 +1091,7 @@ public class Account {
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeAvailableReactions(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedSynchronizeAttachMenuBots(postbox: self.postbox, network: self.network).start())
if !supplementary {
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start())

View File

@ -112,6 +112,7 @@ enum AccountStateMutationOperation {
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
case UpdateGroupCall(peerId: PeerId, call: Api.GroupCall)
case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?)
case UpdateAttachMenuBots
}
struct HoleFromPreviousState {
@ -496,9 +497,13 @@ struct AccountMutableState {
self.addOperation(.UpdateChatListFilter(id: id, filter: filter))
}
mutating func addUpdateAttachMenuBots() {
self.addOperation(.UpdateAttachMenuBots)
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots:
break
case let .AddMessages(messages, location):
for message in messages {

View File

@ -42,6 +42,8 @@ extension ReplyMarkupButton {
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))))
case let .inputKeyboardButtonUserProfile(text, _):
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))))
case let .keyboardButtonWebView(text, url):
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url))
}
}
}

View File

@ -64,6 +64,9 @@ extension TelegramUser {
if (flags & (1 << 21)) != 0 {
botFlags.insert(.requiresGeolocationForInlineRequests)
}
if (flags & (1 << 27)) != 0 {
botFlags.insert(.canBeAddedToAttachMenu)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}
@ -118,6 +121,9 @@ extension TelegramUser {
if (flags & (1 << 21)) != 0 {
botFlags.insert(.requiresGeolocationForInlineRequests)
}
if (flags & (1 << 27)) != 0 {
botFlags.insert(.canBeAddedToAttachMenu)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}

View File

@ -1512,6 +1512,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
})
case let .updateMessageReactions(peer, msgId, reactions):
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate)
case .updateAttachMenuBots:
updatedState.addUpdateAttachMenuBots()
default:
break
}
@ -2304,7 +2306,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -2417,6 +2419,7 @@ func replayFinalState(
var peerActivityTimestamps: [PeerId: Int32] = [:]
var syncChatListFilters = false
var deletedMessageIds: [DeletedMessageId] = []
var syncAttachMenuBots = false
var holesFromPreviousStateMessageIds: [MessageId] = []
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
@ -3322,6 +3325,8 @@ func replayFinalState(
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
case .UpdateAttachMenuBots:
syncAttachMenuBots = true
}
}
@ -3555,6 +3560,10 @@ func replayFinalState(
}
}
if syncAttachMenuBots {
// addSynchronizeAttachMenuBotsOperation(transaction: transaction)
}
for groupId in invalidateGroupStats {
transaction.setNeedsPeerGroupMessageStatsSynchronization(groupId: groupId, namespace: Namespaces.Message.Cloud)
}

View File

@ -83,6 +83,7 @@ public struct Namespaces {
public static let cachedSendAsPeers: Int8 = 18
public static let availableReactions: Int8 = 19
public static let resolvedByPhonePeers: Int8 = 20
public static let attachMenuBots: Int8 = 21
}
public struct UnorderedItemList {

View File

@ -12,6 +12,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case urlAuth(url: String, buttonId: Int32)
case setupPoll(isQuiz: Bool?)
case openUserProfile(peerId: PeerId)
case openWebView(url: String)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("v", orElse: 0) {
@ -37,6 +38,8 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 })
case 10:
self = .openUserProfile(peerId: PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0)))
case 11:
self = .openWebView(url: decoder.decodeStringForKey("u", orElse: ""))
default:
self = .text
}
@ -79,6 +82,9 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case let .openUserProfile(peerId):
encoder.encodeInt32(10, forKey: "v")
encoder.encodeInt64(peerId.toInt64(), forKey: "peerId")
case let .openWebView(url):
encoder.encodeInt32(11, forKey: "v")
encoder.encodeString(url, forKey: "u")
}
}
}

View File

@ -31,6 +31,7 @@ public struct BotUserInfoFlags: OptionSet {
public static let hasAccessToChatHistory = BotUserInfoFlags(rawValue: (1 << 0))
public static let worksWithGroups = BotUserInfoFlags(rawValue: (1 << 1))
public static let requiresGeolocationForInlineRequests = BotUserInfoFlags(rawValue: (1 << 3))
public static let canBeAddedToAttachMenu = BotUserInfoFlags(rawValue: (1 << 4))
}
public struct BotUserInfo: PostboxCoding, Equatable {

View File

@ -0,0 +1,255 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
public final class AttachMenuBots: Equatable, Codable {
public final class Bot: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case peerId
case icon
}
public let peerId: PeerId
public let icon: TelegramMediaFile
public init(
peerId: PeerId,
icon: TelegramMediaFile
) {
self.peerId = peerId
self.icon = icon
}
public static func ==(lhs: Bot, rhs: Bot) -> Bool {
if lhs.peerId != rhs.peerId {
return false
}
if lhs.icon != rhs.icon {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let peerIdValue = try container.decode(Int64.self, forKey: .peerId)
self.peerId = PeerId(peerIdValue)
let iconData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .icon)
self.icon = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: iconData.data)))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.peerId.toInt64(), forKey: .peerId)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.icon), forKey: .icon)
}
}
private enum CodingKeys: String, CodingKey {
case hash
case bots
}
public let hash: Int64
public let bots: [Bot]
public init(
hash: Int64,
bots: [Bot]
) {
self.hash = hash
self.bots = bots
}
public static func ==(lhs: AttachMenuBots, rhs: AttachMenuBots) -> Bool {
if lhs.hash != rhs.hash {
return false
}
if lhs.bots != rhs.bots {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.hash = try container.decode(Int64.self, forKey: .hash)
self.bots = try container.decode([Bot].self, forKey: .bots)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.hash, forKey: .hash)
try container.encode(self.bots, forKey: .bots)
}
}
private func cachedAttachMenuBots(postbox: Postbox) -> Signal<AttachMenuBots?, NoError> {
return postbox.transaction { transaction -> AttachMenuBots? in
return cachedAttachMenuBots(transaction: transaction)
}
}
private func cachedAttachMenuBots(transaction: Transaction) -> AttachMenuBots? {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.attachMenuBots, key: key))?.get(AttachMenuBots.self)
if let cached = cached {
return cached
} else {
return nil
}
}
private func setCachedAttachMenuBots(transaction: Transaction, attachMenuBots: AttachMenuBots) {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
let entryId = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.attachMenuBots, key: key)
if let entry = CodableEntry(attachMenuBots) {
transaction.putItemCacheEntry(id: entryId, entry: entry, collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 10))
} else {
transaction.removeItemCacheEntry(id: entryId)
}
}
func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
let poll = Signal<Never, NoError> { subscriber in
let signal: Signal<Never, NoError> = cachedAttachMenuBots(postbox: postbox)
|> mapToSignal { current in
return (network.request(Api.functions.messages.getAttachMenuBots(hash: current?.hash ?? 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.AttachMenuBots?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
return postbox.transaction { transaction in
switch result {
case let .attachMenuBots(hash, bots, users):
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
var resultBots: [AttachMenuBots.Bot] = []
for bot in bots {
switch bot {
case let .attachMenuBot(botId, attachMenuIcon):
if let icon = telegramMediaFileFromApiDocument(attachMenuIcon) {
resultBots.append(AttachMenuBots.Bot(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), icon: icon))
}
}
}
let attachMenuBots = AttachMenuBots(hash: hash, bots: resultBots)
setCachedAttachMenuBots(transaction: transaction, attachMenuBots: attachMenuBots)
case .attachMenuBotsNotModified:
break
}
} |> ignoreValues
})
}
return signal.start(completed: {
subscriber.putCompletion()
})
}
return (
poll
|> then(
.complete()
|> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())
)
)
|> restart
}
func _internal_addBotToAttachMenu(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<Bool, NoError> {
return postbox.transaction { transaction -> Signal<Bool, NoError> in
guard let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) else {
return .complete()
}
return network.request(Api.functions.messages.toggleBotInAttachMenu(bot: inputUser, enabled: .boolTrue))
|> map { value -> Bool in
switch value {
case .boolTrue:
return true
default:
return false
}
}
|> `catch` { error -> Signal<Bool, NoError> in
return .single(false)
}
|> afterCompleted {
let _ = (managedSynchronizeAttachMenuBots(postbox: postbox, network: network)
|> take(1)).start()
}
}
|> switchToLatest
}
func _internal_removeBotFromAttachMenu(postbox: Postbox, network: Network, peerId: PeerId) -> Signal<Bool, NoError> {
return postbox.transaction { transaction -> Signal<Bool, NoError> in
guard let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) else {
return .complete()
}
return network.request(Api.functions.messages.toggleBotInAttachMenu(bot: inputUser, enabled: .boolFalse))
|> map { value -> Bool in
switch value {
case .boolTrue:
return true
default:
return false
}
}
|> `catch` { error -> Signal<Bool, NoError> in
return .single(false)
}
|> afterCompleted {
let _ = (managedSynchronizeAttachMenuBots(postbox: postbox, network: network)
|> take(1)).start()
}
}
|> switchToLatest
}
public struct AttachMenuBot {
public let peer: Peer
public let icon: TelegramMediaFile
init(peer: Peer, icon: TelegramMediaFile) {
self.peer = peer
self.icon = icon
}
}
func _internal_attachMenuBots(postbox: Postbox) -> Signal<[AttachMenuBot], NoError> {
return postbox.transaction { transaction -> [AttachMenuBot] in
guard let cachedBots = cachedAttachMenuBots(transaction: transaction)?.bots else {
return []
}
var resultBots: [AttachMenuBot] = []
for bot in cachedBots {
if let peer = transaction.getPeer(bot.peerId) {
resultBots.append(AttachMenuBot(peer: peer, icon: bot.icon))
}
}
return resultBots
}
}

View File

@ -0,0 +1,101 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum RequestWebViewResult {
case webViewResult(queryId: Int64, url: String)
case requestConfirmation(botIcon: TelegramMediaFile)
}
public enum RequestWebViewError {
case generic
}
func _internal_requestWebView(postbox: Postbox, network: Network, peerId: PeerId, botId: PeerId, url: String?, themeParams: [String: Any]?) -> Signal<RequestWebViewResult, RequestWebViewError> {
var serializedThemeParams: Api.DataJSON?
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
serializedThemeParams = .dataJSON(data: dataString)
}
return postbox.transaction { transaction -> Signal<RequestWebViewResult, RequestWebViewError> in
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let bot = transaction.getPeer(botId), let inputUser = apiInputUser(bot) else {
return .fail(.generic)
}
var flags: Int32 = 0
if let _ = url {
flags |= (1 << 0)
}
if let _ = serializedThemeParams {
flags |= (1 << 1)
}
return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputUser, url: url, themeParams: serializedThemeParams))
|> mapError { _ -> RequestWebViewError in
return .generic
}
|> mapToSignal { result -> Signal<RequestWebViewResult, RequestWebViewError> in
switch result {
case let .webViewResultConfirmationRequired(bot, users):
return postbox.transaction { transaction -> Signal<RequestWebViewResult, RequestWebViewError> in
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
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))
}
}
}
|> castError(RequestWebViewError.self)
|> switchToLatest
}
public enum GetWebViewResultError {
case generic
}
func _internal_getWebViewResult(postbox: Postbox, network: Network, peerId: PeerId, botId: PeerId, queryId: Int64) -> Signal<ChatContextResult, GetWebViewResultError> {
return postbox.transaction { transaction -> Signal<ChatContextResult, GetWebViewResultError> in
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let bot = transaction.getPeer(botId), let inputUser = apiInputUser(bot) else {
return .fail(.generic)
}
return network.request(Api.functions.messages.getWebViewResult(peer: inputPeer, bot: inputUser, queryId: queryId))
|> mapError { _ -> GetWebViewResultError in
return .generic
}
|> mapToSignal { result -> Signal<ChatContextResult, GetWebViewResultError> in
return postbox.transaction { transaction -> ChatContextResult in
switch result {
case let .webViewResult(result, users):
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
return ChatContextResult(apiResult: result, queryId: queryId)
}
}
|> castError(GetWebViewResultError.self)
}
}
|> castError(GetWebViewResultError.self)
|> switchToLatest
}

View File

@ -2,19 +2,19 @@ import Foundation
import Postbox
import SwiftSignalKit
func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, results: ChatContextResultCollection, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> Bool {
guard let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else {
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 = _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, results: ChatContextResultCollection, 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 {
attributes.append(InlineBotMessageAttribute(peerId: results.botId, title: nil))
attributes.append(InlineBotMessageAttribute(peerId: botId, title: nil))
}
if let scheduleTime = scheduleTime {
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))

View File

@ -160,8 +160,12 @@ public extension TelegramEngine {
return _internal_exportMessageLink(account: self.account, peerId: peerId, messageId: messageId, isThread: isThread)
}
public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, results: ChatContextResultCollection, 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, results: results, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId)
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<RequestChatContextResultsResult?, RequestChatContextResultsError> {
@ -317,5 +321,25 @@ public extension TelegramEngine {
public func translate(text: String, fromLang: String?, toLang: String) -> Signal<String?, NoError> {
return _internal_translate(network: self.account.network, text: text, fromLang: fromLang, toLang: toLang)
}
public func requestWebView(peerId: PeerId, botId: PeerId, url: String?, themeParams: [String: Any]?) -> Signal<RequestWebViewResult, RequestWebViewError> {
return _internal_requestWebView(postbox: self.account.postbox, network: self.account.network, peerId: peerId, botId: botId, url: url, themeParams: themeParams)
}
public func getWebViewResult(peerId: PeerId, botId: PeerId, queryId: Int64) -> Signal<ChatContextResult, GetWebViewResultError> {
return _internal_getWebViewResult(postbox: self.account.postbox, network: self.account.network, peerId: peerId, botId: botId, queryId: queryId)
}
public func addBotToAttachMenu(peerId: PeerId) -> Signal<Bool, NoError> {
return _internal_addBotToAttachMenu(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
}
public func removeBotFromAttachMenu(peerId: PeerId) -> Signal<Bool, NoError> {
return _internal_removeBotFromAttachMenu(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
}
public func attachMenuBots() -> Signal<[AttachMenuBot], NoError> {
return _internal_attachMenuBots(postbox: self.account.postbox)
}
}
}

View File

@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "attachalert_48.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -215,6 +215,8 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
self.controllerInteraction.openPollCreation(isQuiz)
case let .openUserProfile(peerId):
self.controllerInteraction.openPeer(peerId, .info, nil, nil)
case let .openWebView(url):
self.controllerInteraction.openWebView(url)
}
if dismissIfOnce {
if let message = self.message {

View File

@ -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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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
@ -3299,6 +3301,77 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil)
}, openWebView: { [weak self] url in
guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return
}
let botName = EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
let updateProgress = { [weak self] in
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: url, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme))
|> afterDisposed {
updateProgress()
})
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
switch result {
case let .webViewResult(queryId, url):
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: queryId)
controller.navigationPresentation = .modal
strongSelf.push(controller)
case .requestConfirmation:
break
}
}, error: { [weak self] error in
if let strongSelf = self {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}))
}, requestMessageUpdate: { [weak self] id in
if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
@ -9043,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) {
@ -10384,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
}
@ -10415,11 +10497,45 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
var availableTabs: [AttachmentButtonType] = [.gallery, .file, .location, .contact]
var availableButtons: [AttachmentButtonType] = [.gallery, .file, .location, .contact]
if canSendPolls {
availableTabs.insert(.poll, at: availableTabs.count - 1)
availableButtons.insert(.poll, at: availableButtons.count - 1)
}
let presentationData = self.presentationData
var switchToBotImpl: ((AttachmentButtonType) -> Void)?
var switchToBotId = botId
let buttons: Signal<[AttachmentButtonType], NoError>
if let _ = peer as? TelegramUser {
buttons = .single(availableButtons)
|> then(
self.context.engine.messages.attachMenuBots()
|> map { attachMenuBots in
var buttons = availableButtons
for bot in attachMenuBots.reversed() {
let peerTitle = EnginePeer(bot.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
buttons.insert(.app(bot.peer.id, peerTitle, bot.icon), at: 1)
}
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)
}
// availableTabs.insert(.app("Web App"), at: 1)
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
@ -10427,7 +10543,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: self.chatLocation, buttons: availableTabs)
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
@ -10684,8 +10803,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let controller = strongSelf.configurePollCreation()
completion(controller, nil)
strongSelf.controllerNavigationDisposable.set(nil)
case .app:
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, url: "", message: nil)
case let .app(botId, botName, _):
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, botId: botId, botName: botName, url: nil, queryId: nil)
completion(controller, nil)
strongSelf.controllerNavigationDisposable.set(nil)
}
@ -12222,7 +12341,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId
if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, results: results, result: result, replyToMessageId: replyMessageId, hideVia: hideVia, silentPosting: silentPosting) {
if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, botId: results.botId, result: result, replyToMessageId: replyMessageId, hideVia: hideVia, silentPosting: silentPosting) {
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
@ -13693,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
}
@ -13747,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 {
@ -14171,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
}
@ -14204,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
}

View File

@ -131,6 +131,7 @@ public final class ChatControllerInteraction {
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void
let openJoinLink: (String) -> Void
let openWebView: (String) -> Void
let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void
@ -230,6 +231,7 @@ public final class ChatControllerInteraction {
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void,
openJoinLink: @escaping (String) -> Void,
openWebView: @escaping (String) -> Void,
requestMessageUpdate: @escaping (MessageId) -> Void,
cancelInteractiveKeyboardGestures: @escaping () -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
@ -315,6 +317,7 @@ public final class ChatControllerInteraction {
self.commitEmojiInteraction = commitEmojiInteraction
self.openLargeEmojiInfo = openLargeEmojiInfo
self.openJoinLink = openJoinLink
self.openWebView = openWebView
self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@ -374,6 +377,7 @@ public final class ChatControllerInteraction {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -2568,13 +2568,25 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.historyNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: -1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
if let emptyNode = self.emptyNode {
emptyNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
}
UIGraphicsPopContext()
}).flatMap(applyScreenshotEffectToImage)
let blurredHistoryNode = ASImageNode()
blurredHistoryNode.image = image
blurredHistoryNode.frame = self.historyNode.frame
self.blurredHistoryNode = blurredHistoryNode
self.historyNode.supernode?.insertSubnode(blurredHistoryNode, aboveSubnode: self.historyNode)
if let emptyNode = self.emptyNode {
emptyNode.supernode?.insertSubnode(blurredHistoryNode, aboveSubnode: emptyNode)
} else {
self.historyNode.supernode?.insertSubnode(blurredHistoryNode, aboveSubnode: self.historyNode)
}
}
} else {
if let blurredHistoryNode = self.blurredHistoryNode {

View File

@ -112,7 +112,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
case .openUserProfile:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage
case .openWebApp:
case .openWebView:
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingWebAppIconImage : graphics.chatBubbleActionButtonOutgoingWebAppIconImage
default:
iconImage = nil

View File

@ -854,6 +854,8 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
break
case let .openUserProfile(peerId):
item.controllerInteraction.openPeer(peerId, .info, nil, nil)
case let .openWebView(url):
item.controllerInteraction.openWebView(url)
}
}
}

View File

@ -532,6 +532,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
@ -937,6 +938,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}), .window(.root), nil)
case .importStickers:
break
case .setAttach:
break
}
}
}))

View File

@ -159,6 +159,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -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 {

View File

@ -25,6 +25,7 @@ import UndoUI
import ImportStickerPackUI
import PeerInfoUI
import Markdown
import WebUI
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
if case .default = navigation {
@ -44,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<PresentationData, NoError>)?
if case let .chat(maybeUpdatedPresentationData) = urlContext {
if case let .chat(_, maybeUpdatedPresentationData) = urlContext {
updatedPresentationData = maybeUpdatedPresentationData
} else {
updatedPresentationData = nil
@ -546,5 +547,43 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
present(controller, nil)
}
}
case let .setAttach(peerId):
let presentError: (String) -> Void = { errorText in
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
let _ = (context.engine.messages.attachMenuBots()
|> deliverOnMainQueue).start(next: { attachMenuBots in
if let _ = attachMenuBots.firstIndex(where: { $0.peer.id == peerId }) {
presentError(presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError)
} else {
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
)
|> deliverOnMainQueue).start(next: { peer in
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)
}
}
})
})
}
})
}
}

View File

@ -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
}
@ -614,6 +619,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
var game: String?
var post: String?
var voiceChat: String?
var attach: String?
var setAttach: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
@ -633,9 +640,13 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
post = value
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
voiceChat = value
} else if queryItem.name == "attach" {
attach = value
}
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
voiceChat = ""
} else if queryItem.name == "setattach" {
setAttach = ""
}
}
}
@ -662,6 +673,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
} else {
result += "?voicechat="
}
} else if let attach = attach {
result += "?attach=\(attach)"
} else if let _ = setAttach {
result += "?setattach"
}
convertedUrl = result
}

View File

@ -151,6 +151,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))

View File

@ -926,24 +926,25 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
}
if let encryptionKeyFingerprint = data.encryptionKeyFingerprint {
items[.peerInfo]!.append(PeerInfoScreenDisclosureEncryptionKeyItem(id: 5, text: presentationData.strings.Profile_EncryptionKey, fingerprint: encryptionKeyFingerprint, action: {
interaction.openEncryptionKey()
}))
}
if user.botInfo != nil, !user.isVerified {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 6, text: presentationData.strings.ReportPeer_Report, action: {
interaction.openReport(false)
}))
}
if let botInfo = user.botInfo, botInfo.flags.contains(.worksWithGroups) {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 5, text: presentationData.strings.Bot_AddToChat, color: .accent, action: {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 7, text: presentationData.strings.Bot_AddToChat, color: .accent, action: {
interaction.openAddBotToGroup()
}))
items[.peerInfo]!.append(PeerInfoScreenCommentItem(id: 6, text: presentationData.strings.Bot_AddToChatInfo))
items[.peerInfo]!.append(PeerInfoScreenCommentItem(id: 8, text: presentationData.strings.Bot_AddToChatInfo))
}
}
if let encryptionKeyFingerprint = data.encryptionKeyFingerprint {
items[.peerInfo]!.append(PeerInfoScreenDisclosureEncryptionKeyItem(id: 6, text: presentationData.strings.Profile_EncryptionKey, fingerprint: encryptionKeyFingerprint, action: {
interaction.openEncryptionKey()
}))
}
if user.botInfo != nil, !user.isVerified {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 5, text: presentationData.strings.ReportPeer_Report, action: {
interaction.openReport(false)
}))
}
} else if let channel = data.peer as? TelegramChannel {
let ItemUsername = 1
@ -2265,6 +2266,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
@ -3284,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
}
@ -3303,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
}
@ -3379,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))
}
}
}

View File

@ -1325,6 +1325,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, openWebView: { _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -66,6 +66,7 @@ extension ResolvedBotAdminRights {
public enum ParsedInternalPeerUrlParameter {
case botStart(String)
case groupBotStart(String, ResolvedBotAdminRights?)
case attachBotStart(String)
case channelMessage(Int32, Double?)
case replyThread(Int32, Int32)
case voiceChat(String?)
@ -86,6 +87,7 @@ public enum ParsedInternalUrl {
case wallpaper(WallpaperUrlParameter)
case theme(String)
case phone(String)
case setAttach(String)
}
private enum ParsedUrl {
@ -182,7 +184,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} else {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "start" {
if queryItem.name == "attach" {
return .peerName(peerName, .attachBotStart(value))
} else if queryItem.name == "start" {
return .peerName(peerName, .botStart(value))
} else if queryItem.name == "startgroup" {
var botAdminRights: ResolvedBotAdminRights?
@ -200,6 +204,8 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
}
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
return .peerName(peerName, .voiceChat(nil))
} else if queryItem.name == "setattach" {
return .setAttach(peerName)
}
}
}
@ -431,6 +437,19 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.botStart(peerId: peer.id, payload: payload))
case let .groupBotStart(payload, adminRights):
return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights))
case let .attachBotStart(name):
return context.engine.peers.resolvePeerByName(name: name)
|> take(1)
|> mapToSignal { botPeer -> Signal<Peer?, NoError> in
return .single(botPeer?._asPeer())
}
|> mapToSignal { botPeer -> Signal<ResolvedUrl?, NoError> in
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)))
}
}
case let .channelMessage(id, timecode):
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))
case let .replyThread(id, replyId):
@ -523,6 +542,19 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.wallpaper(parameter))
case let .theme(slug):
return .single(.theme(slug))
case let .setAttach(name):
return context.engine.peers.resolvePeerByName(name: name)
|> take(1)
|> mapToSignal { peer -> Signal<Peer?, NoError> in
return .single(peer?._asPeer())
}
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
if let peer = peer {
return .single(.setAttach(peer.id))
} else {
return .single(.inaccessiblePeer)
}
}
}
}

View File

@ -15,9 +15,12 @@ swift_library(
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/AccountContext:AccountContext",
"//submodules/AttachmentUI:AttachmentUI",
"//submodules/CounterContollerTitleView:CounterContollerTitleView",
"//submodules/HexColor:HexColor",
"//submodules/PhotoResources:PhotoResources",
],
visibility = [
"//visibility:public",

View File

@ -9,11 +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
@ -22,16 +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, 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
@ -56,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)
@ -69,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("Web App").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.textNode.attributedText = NSAttributedString(string: strings.WebApp_AddToAttachmentText(self.peerName).string, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
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 {
@ -187,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)
@ -197,8 +225,8 @@ private final class WebAppAlertContentNode: AlertContentNode {
}
}
func addWebAppToAttachmentController(sharedContext: SharedAccountContext) -> 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
@ -209,9 +237,10 @@ func addWebAppToAttachmentController(sharedContext: SharedAccountContext) -> Ale
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: {
dismissImpl?(true)
completion()
})]
contentNode = WebAppAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, 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

View File

@ -3,6 +3,7 @@ import UIKit
import WebKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
@ -10,6 +11,8 @@ import AccountContext
import AttachmentUI
import CounterContollerTitleView
import ContextUI
import PresentationDataUtils
import HexColor
private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler {
private let f: (WKScriptMessage) -> ()
@ -25,48 +28,75 @@ private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler {
}
}
public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] {
var backgroundColor = presentationTheme.list.plainBackgroundColor.rgb
if backgroundColor == 0x000000 {
backgroundColor = presentationTheme.list.itemBlocksBackgroundColor.rgb
}
return [
"bg_color": Int32(bitPattern: backgroundColor),
"text_color": Int32(bitPattern: presentationTheme.list.itemPrimaryTextColor.rgb),
"hint_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb),
"link_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb),
"button_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.fillColor.rgb),
"button_text_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.foregroundColor.rgb)
]
}
public final class WebAppController: ViewController, AttachmentContainable {
public var requestAttachmentMenuExpansion: () -> Void = { }
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
public var cancelPanGesture: () -> Void = { }
private class Node: ViewControllerTracingNode {
private class Node: ViewControllerTracingNode, UIScrollViewDelegate {
private weak var controller: WebAppController?
private var webView: WKWebView?
private let context: AccountContext
var presentationData: PresentationData
private let present: (ViewController, Any?) -> Void
private let message: EngineMessage?
private var queryId: Int64?
init(context: AccountContext, presentationData: PresentationData, url: String, present: @escaping (ViewController, Any?) -> Void, message: EngineMessage?) {
init(context: AccountContext, controller: WebAppController, presentationData: PresentationData, peerId: PeerId, botId: PeerId, url: String?, queryId: Int64?, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.controller = controller
self.presentationData = presentationData
self.present = present
self.message = message
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()
let js = "var TelegramWebviewProxyProto = function() {}; " +
"TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " +
"window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " +
"}; " +
"var TelegramWebviewProxy = new TelegramWebviewProxyProto();"
let configuration = WKWebViewConfiguration()
let userController = WKUserContentController()
let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false)
userController.addUserScript(userScript)
userController.add(WeakGameScriptMessageHandler { [weak self] message in
if let strongSelf = self {
strongSelf.handleScriptMessage(message)
}
}, name: "performAction")
let selectionString = "var css = '*{-webkit-touch-callout:none;-webkit-user-select:none}';"
+ " var head = document.head || document.getElementsByTagName('head')[0];"
+ " var style = document.createElement('style'); style.type = 'text/css';" +
" style.appendChild(document.createTextNode(css)); head.appendChild(style);"
let selectionScript: WKUserScript = WKUserScript(source: selectionString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
userController.addUserScript(selectionScript)
configuration.userContentController = userController
configuration.allowsInlineMediaPlayback = true
@ -79,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
}
@ -88,18 +121,62 @@ public final class WebAppController: ViewController, AttachmentContainable {
webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in
return point.x > 30.0
}
self.view.addSubview(webView)
webView.allowsBackForwardNavigationGestures = false
webView.scrollView.delegate = self
self.webView = webView
if let parsedUrl = URL(string: url) {
webView.load(URLRequest(url: parsedUrl))
if let url = url, let queryId = queryId {
self.queryId = queryId
if let parsedUrl = URL(string: url) {
self.webView?.load(URLRequest(url: parsedUrl))
}
} else {
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
}
switch result {
case let .webViewResult(queryId, url):
if let parsedUrl = URL(string: url) {
strongSelf.queryId = queryId
strongSelf.webView?.load(URLRequest(url: parsedUrl))
}
case .requestConfirmation:
break
}
})
}
}
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)
}
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)))
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)))
}
}
@ -113,34 +190,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
})
}
private func shareData() -> (EnginePeer, String)? {
guard let message = self.message else {
return nil
}
var botPeer: EnginePeer?
var gameName: String?
for media in message.media {
if let game = media as? TelegramMediaGame {
inner: for attribute in message.attributes {
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId {
botPeer = message.peers[peerId].flatMap(EnginePeer.init)
break inner
}
}
if botPeer == nil {
botPeer = message.author
}
gameName = game.name
}
}
if let botPeer = botPeer, let gameName = gameName {
return (botPeer, gameName)
}
return nil
}
private func handleScriptMessage(_ message: WKScriptMessage) {
guard let body = message.body as? [String: Any] else {
return
@ -150,15 +199,67 @@ public final class WebAppController: ViewController, AttachmentContainable {
return
}
if eventName == "share_game" || eventName == "share_score" {
if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
if eventName == "share_score" {
} else {
switch eventName {
case "webview_send_result_message":
self.handleSendResultMessage()
case "webview_close":
self.controller?.dismiss()
default:
break
}
}
func sendEvent(name: String, data: String) {
let script = "window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data))"
self.webView?.evaluateJavaScript(script, completionHandler: { _, _ in
})
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
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 {
let color = UIColor(rgb: UInt32(bitPattern: value))
if themeParamsString.count > 1 {
themeParamsString.append(", ")
}
themeParamsString.append("\"\(key)\": \"#\(color.hexString)\"")
}
}
themeParamsString.append("}")
self.sendEvent(name: "theme_changed", data: themeParamsString)
}
private func handleSendResultMessage() {
guard let controller = self.controller, let queryId = self.queryId else {
return
}
let _ = (self.context.engine.messages.getWebViewResult(peerId: controller.peerId, botId: controller.botId, queryId: queryId)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self, let controller = strongSelf.controller else {
return
}
controller.present(textAlertController(context: strongSelf.context, updatedPresentationData: controller.updatedPresentationData, title: nil, text: "Send result?", actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { [weak self] in
guard let strongSelf = self, let controller = strongSelf.controller else {
return
}
let _ = strongSelf.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: controller.peerId, botId: controller.botId, result: result)
controller.dismiss()
})]), in: .window(.root))
})
}
}
@ -167,23 +268,35 @@ public final class WebAppController: ViewController, AttachmentContainable {
return self.displayNode as! Node
}
private var titleView: CounterContollerTitleView?
private let moreButtonNode: MoreButtonNode
private let context: AccountContext
private let url: String
private let message: EngineMessage?
private let peerId: PeerId
private let botId: PeerId
private let url: String?
private let queryId: Int64?
private var presentationData: PresentationData
fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private var presentationDataDisposable: Disposable?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, url: String, message: EngineMessage?) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, botId: PeerId, botName: String, url: String?, queryId: Int64?) {
self.context = context
self.peerId = peerId
self.botId = botId
self.url = url
self.message = message
self.queryId = queryId
self.updatedPresentationData = updatedPresentationData
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
var theme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
theme = theme.withUpdatedBackgroundColor(self.presentationData.theme.list.plainBackgroundColor)
if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
theme = theme.withUpdatedBackgroundColor(self.presentationData.theme.list.itemBlocksBackgroundColor)
} else {
theme = theme.withUpdatedBackgroundColor(self.presentationData.theme.list.plainBackgroundColor)
}
let navigationBarPresentationData = NavigationBarPresentationData(theme: theme, strings: NavigationBarStrings(back: "", close: ""))
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
@ -199,14 +312,34 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.navigationItem.rightBarButtonItem?.target = self
let titleView = CounterContollerTitleView(theme: self.presentationData.theme)
titleView.title = CounterContollerTitle(title: "Web App", counter: self.presentationData.strings.Bot_GenericBotStatus)
titleView.title = CounterContollerTitle(title: botName, counter: self.presentationData.strings.Bot_GenericBotStatus)
self.navigationItem.titleView = titleView
self.titleView = titleView
self.moreButtonNode.action = { [weak self] _, gesture in
if let strongSelf = self {
strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
}
}
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
strongSelf.presentationData = presentationData
var theme = NavigationBarTheme(rootControllerTheme: presentationData.theme)
if presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
theme = theme.withUpdatedBackgroundColor(presentationData.theme.list.itemBlocksBackgroundColor)
} else {
theme = theme.withUpdatedBackgroundColor(presentationData.theme.list.plainBackgroundColor)
}
let navigationBarPresentationData = NavigationBarPresentationData(theme: theme, strings: NavigationBarStrings(back: "", close: ""))
strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData)
strongSelf.titleView?.theme = presentationData.theme
strongSelf.controllerNode.updatePresentationData(presentationData)
}
})
}
required public init(coder aDecoder: NSCoder) {
@ -215,6 +348,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
deinit {
assert(true)
self.presentationDataDisposable?.dispose()
}
@objc private func cancelPressed() {
@ -226,42 +360,61 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: "Open Bot", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
let controller = addWebAppToAttachmentController(sharedContext: strongSelf.context.sharedContext)
strongSelf.present(controller, in: .window(.root))
})))
items.append(.action(ContextMenuActionItem(text: "Reload Page", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
})))
let context = self.context
let presentationData = self.presentationData
items.append(.action(ContextMenuActionItem(text: "Remove Bot", textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { _, f in
f(.default)
let peerId = self.peerId
let botId = self.botId
let items = context.engine.messages.attachMenuBots()
|> map { attachMenuBots -> ContextController.Items in
var items: [ContextMenuItem] = []
if peerId != botId {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_OpenBot, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
// if let strongSelf = self {
// strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf., context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId)))
// }
})))
}
})))
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(WebAppContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebApp_ReloadPage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
})))
if let _ = attachMenuBots.firstIndex(where: { $0.peer.id == botId}) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
let _ = context.engine.messages.removeBotFromAttachMenu(peerId: strongSelf.botId).start()
strongSelf.dismiss()
}
})))
}
return ContextController.Items(content: .list(items))
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(WebAppContextReferenceContentSource(controller: self, sourceNode: node)), items: items, gesture: gesture)
self.presentInGlobalOverlay(contextController)
}
override public func loadDisplayNode() {
self.displayNode = Node(context: self.context, presentationData: self.presentationData, url: self.url, present: { [weak self] c, a in
self.displayNode = Node(context: self.context, controller: self, presentationData: self.presentationData, peerId: self.peerId, botId: self.botId, url: self.url, queryId: self.queryId, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, message: self.message)
})
self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {