mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 11:50:56 +00:00
Merge commit 'befb570fa73353506d67344e752b47480f9af26d'
This commit is contained in:
commit
23bb1a4c5f
@ -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.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.AddToAttachmentAdd" = "Add";
|
||||||
|
|
||||||
|
"WebApp.AddToAttachmentAlreadyAddedError" = "This bot is already added in the attachment menu.";
|
||||||
|
|
||||||
|
|||||||
@ -117,7 +117,7 @@ public final class AccountWithInfo: Equatable {
|
|||||||
|
|
||||||
public enum OpenURLContext {
|
public enum OpenURLContext {
|
||||||
case generic
|
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 {
|
public struct ChatAvailableMessageActionOptions: OptionSet {
|
||||||
@ -188,10 +188,10 @@ public struct ResolvedBotAdminRights: OptionSet {
|
|||||||
public static let pinMessages = ResolvedBotAdminRights(rawValue: 128)
|
public static let pinMessages = ResolvedBotAdminRights(rawValue: 128)
|
||||||
public static let promoteMembers = ResolvedBotAdminRights(rawValue: 256)
|
public static let promoteMembers = ResolvedBotAdminRights(rawValue: 256)
|
||||||
public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512)
|
public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512)
|
||||||
public static let manageChat = ResolvedBotAdminRights(rawValue: 1024)
|
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024)
|
||||||
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 2048)
|
public static let manageChat = ResolvedBotAdminRights(rawValue: 2048)
|
||||||
|
|
||||||
public var chatAdminRights: TelegramChatAdminRightsFlags {
|
public var chatAdminRights: TelegramChatAdminRightsFlags? {
|
||||||
var flags = TelegramChatAdminRightsFlags()
|
var flags = TelegramChatAdminRightsFlags()
|
||||||
|
|
||||||
if self.contains(ResolvedBotAdminRights.changeInfo) {
|
if self.contains(ResolvedBotAdminRights.changeInfo) {
|
||||||
@ -224,6 +224,11 @@ public struct ResolvedBotAdminRights: OptionSet {
|
|||||||
if self.contains(ResolvedBotAdminRights.canBeAnonymous) {
|
if self.contains(ResolvedBotAdminRights.canBeAnonymous) {
|
||||||
flags.insert(.canBeAnonymous)
|
flags.insert(.canBeAnonymous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,6 +258,7 @@ public enum ResolvedUrl {
|
|||||||
case settings(ResolvedUrlSettingsSection)
|
case settings(ResolvedUrlSettingsSection)
|
||||||
case joinVoiceChat(PeerId, String?)
|
case joinVoiceChat(PeerId, String?)
|
||||||
case importStickers
|
case importStickers
|
||||||
|
case setAttach(PeerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NavigateToChatKeepStack {
|
public enum NavigateToChatKeepStack {
|
||||||
@ -340,6 +346,7 @@ public final class NavigateToChatControllerParams {
|
|||||||
public let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
public let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
||||||
public let subject: ChatControllerSubject?
|
public let subject: ChatControllerSubject?
|
||||||
public let botStart: ChatControllerInitialBotStart?
|
public let botStart: ChatControllerInitialBotStart?
|
||||||
|
public let attachBotId: PeerId?
|
||||||
public let updateTextInputState: ChatTextInputState?
|
public let updateTextInputState: ChatTextInputState?
|
||||||
public let activateInput: Bool
|
public let activateInput: Bool
|
||||||
public let keepStack: NavigateToChatKeepStack
|
public let keepStack: NavigateToChatKeepStack
|
||||||
@ -358,7 +365,7 @@ public final class NavigateToChatControllerParams {
|
|||||||
public let changeColors: Bool
|
public let changeColors: Bool
|
||||||
public let completion: (ChatController) -> Void
|
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.navigationController = navigationController
|
||||||
self.chatController = chatController
|
self.chatController = chatController
|
||||||
self.chatLocationContextHolder = chatLocationContextHolder
|
self.chatLocationContextHolder = chatLocationContextHolder
|
||||||
@ -366,6 +373,7 @@ public final class NavigateToChatControllerParams {
|
|||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.botStart = botStart
|
self.botStart = botStart
|
||||||
|
self.attachBotId = attachBotId
|
||||||
self.updateTextInputState = updateTextInputState
|
self.updateTextInputState = updateTextInputState
|
||||||
self.activateInput = activateInput
|
self.activateInput = activateInput
|
||||||
self.keepStack = keepStack
|
self.keepStack = keepStack
|
||||||
|
|||||||
@ -148,6 +148,7 @@ public enum ChatControllerInteractionNavigateToPeer {
|
|||||||
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?)
|
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?)
|
||||||
case info
|
case info
|
||||||
case withBotStartPayload(ChatControllerInitialBotStart)
|
case withBotStartPayload(ChatControllerInitialBotStart)
|
||||||
|
case withAttachBot(PeerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ChatInterfaceForwardOptionsState: Codable, Equatable {
|
public struct ChatInterfaceForwardOptionsState: Codable, Equatable {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ swift_library(
|
|||||||
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
|
"//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
|
||||||
"//submodules/ContextUI:ContextUI",
|
"//submodules/ContextUI:ContextUI",
|
||||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||||
|
"//submodules/PhotoResources:PhotoResources",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -123,6 +123,9 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UILongPressGestureRecognizer {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public enum AttachmentButtonType: Equatable {
|
|||||||
case location
|
case location
|
||||||
case contact
|
case contact
|
||||||
case poll
|
case poll
|
||||||
case app(String)
|
case app(PeerId, String, TelegramMediaFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol AttachmentContainable: ViewController {
|
public protocol AttachmentContainable: ViewController {
|
||||||
@ -97,7 +97,7 @@ public class AttachmentController: ViewController {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
private let chatLocation: ChatLocation
|
private let chatLocation: ChatLocation
|
||||||
private let buttons: [AttachmentButtonType]
|
private var buttons: [AttachmentButtonType]
|
||||||
|
|
||||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||||
get {
|
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 {
|
if let strongSelf = self {
|
||||||
return strongSelf.switchToController(type, ascending)
|
return strongSelf.switchToController(type)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -268,7 +268,7 @@ public class AttachmentController: ViewController {
|
|||||||
|
|
||||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
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) {
|
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 {
|
guard self.currentType != type else {
|
||||||
if self.animating {
|
if self.animating {
|
||||||
return false
|
return false
|
||||||
@ -338,13 +357,13 @@ public class AttachmentController: ViewController {
|
|||||||
let previousController = strongSelf.currentControllers.last
|
let previousController = strongSelf.currentControllers.last
|
||||||
strongSelf.currentControllers = [controller]
|
strongSelf.currentControllers = [controller]
|
||||||
|
|
||||||
if previousType != nil {
|
if previousType != nil && animated {
|
||||||
strongSelf.animateSwitchTransition(controller, previousController: previousController)
|
strongSelf.animateSwitchTransition(controller, previousController: previousController)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let layout = strongSelf.validLayout {
|
if let layout = strongSelf.validLayout {
|
||||||
strongSelf.switchingController = true
|
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
|
strongSelf.switchingController = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -564,11 +583,13 @@ public class AttachmentController: ViewController {
|
|||||||
completion(nil, nil)
|
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.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
self.buttons = buttons
|
self.buttons = []
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -581,10 +602,21 @@ public class AttachmentController: ViewController {
|
|||||||
strongSelf.node.scrollToTop()
|
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 {
|
deinit {
|
||||||
print()
|
self.buttonsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init(coder aDecoder: NSCoder) {
|
public required init(coder aDecoder: NSCoder) {
|
||||||
@ -600,6 +632,10 @@ public class AttachmentController: ViewController {
|
|||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func switchTo(_ type: AttachmentButtonType) {
|
||||||
|
(self.displayNode as! Node).switchTo(type)
|
||||||
|
}
|
||||||
|
|
||||||
public func _dismiss() {
|
public func _dismiss() {
|
||||||
super.dismiss(animated: false, completion: {})
|
super.dismiss(animated: false, completion: {})
|
||||||
}
|
}
|
||||||
@ -621,9 +657,12 @@ public class AttachmentController: ViewController {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.validLayout = layout
|
||||||
self.node.containerLayoutUpdated(layout, transition: transition)
|
self.node.containerLayoutUpdated(layout, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,11 +12,102 @@ import AttachmentTextInputPanelNode
|
|||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
import ChatSendMessageActionUI
|
import ChatSendMessageActionUI
|
||||||
import ChatTextLinkEditUI
|
import ChatTextLinkEditUI
|
||||||
|
import PhotoResources
|
||||||
|
|
||||||
private let buttonSize = CGSize(width: 88.0, height: 49.0)
|
private let buttonSize = CGSize(width: 88.0, height: 49.0)
|
||||||
private let iconSize = CGSize(width: 30.0, height: 30.0)
|
private let iconSize = CGSize(width: 30.0, height: 30.0)
|
||||||
private let sideInset: CGFloat = 0.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 {
|
private final class AttachButtonComponent: CombinedComponent {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let type: AttachmentButtonType
|
let type: AttachmentButtonType
|
||||||
@ -61,13 +152,14 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
let icon = Child(Image.self)
|
let icon = Child(IconComponent.self)
|
||||||
let title = Child(Text.self)
|
let title = Child(Text.self)
|
||||||
let button = Child(Rectangle.self)
|
let button = Child(Rectangle.self)
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
let name: String
|
let name: String
|
||||||
let imageName: String?
|
let imageName: String
|
||||||
|
var imageFile: TelegramMediaFile?
|
||||||
|
|
||||||
let component = context.component
|
let component = context.component
|
||||||
let strings = component.strings
|
let strings = component.strings
|
||||||
@ -88,20 +180,23 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
case .poll:
|
case .poll:
|
||||||
name = strings.Attachment_Poll
|
name = strings.Attachment_Poll
|
||||||
imageName = "Chat/Attach Menu/Poll"
|
imageName = "Chat/Attach Menu/Poll"
|
||||||
case let .app(appName):
|
case let .app(_, appName, appIcon):
|
||||||
name = appName
|
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 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(
|
let icon = icon.update(
|
||||||
component: Image(
|
component: IconComponent(
|
||||||
image: image,
|
account: component.context.account,
|
||||||
|
name: imageName,
|
||||||
|
file: imageFile,
|
||||||
tintColor: tintColor
|
tintColor: tintColor
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
availableSize: iconSize,
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -128,7 +223,7 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
let topInset: CGFloat = 4.0 + UIScreenPixel
|
let topInset: CGFloat = 4.0 + UIScreenPixel
|
||||||
let spacing: CGFloat = 15.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)
|
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
|
context.add(title
|
||||||
@ -174,7 +269,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
private var scrollLayout: (width: CGFloat, contentSize: CGSize)?
|
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 beganTextEditing: () -> Void = {}
|
||||||
var textUpdated: (NSAttributedString) -> Void = { _ in }
|
var textUpdated: (NSAttributedString) -> Void = { _ in }
|
||||||
var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> 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) {
|
func updateViews(transition: Transition) {
|
||||||
guard let layout = self.validLayout else {
|
guard let layout = self.validLayout else {
|
||||||
return
|
return
|
||||||
@ -480,8 +580,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
theme: self.presentationData.theme,
|
theme: self.presentationData.theme,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let ascending = i > strongSelf.selectedIndex
|
if strongSelf.selectionChanged(type) {
|
||||||
if strongSelf.selectionChanged(type, ascending) {
|
|
||||||
strongSelf.selectedIndex = i
|
strongSelf.selectedIndex = i
|
||||||
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,24 +15,33 @@ public struct CounterContollerTitle: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class CounterContollerTitleView: UIView {
|
public final class CounterContollerTitleView: UIView {
|
||||||
private var theme: PresentationTheme
|
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let subtitleNode: ImmediateTextNode
|
private let subtitleNode: ImmediateTextNode
|
||||||
|
|
||||||
public var title: CounterContollerTitle = CounterContollerTitle(title: "", counter: "") {
|
public var title: CounterContollerTitle = CounterContollerTitle(title: "", counter: "") {
|
||||||
didSet {
|
didSet {
|
||||||
if self.title != oldValue {
|
if self.title != oldValue {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: self.title.title, font: Font.semibold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
self.update()
|
||||||
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 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) {
|
public init(theme: PresentationTheme) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
|
|||||||
@ -1248,6 +1248,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if let navigationController = strongSelf.getNavigationController() {
|
if let navigationController = strongSelf.getNavigationController() {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: botStart, keepStack: .always))
|
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:
|
case .info:
|
||||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId)
|
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId)
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
|||||||
@ -24,6 +24,7 @@ swift_library(
|
|||||||
"//submodules/WebPBinding:WebPBinding",
|
"//submodules/WebPBinding:WebPBinding",
|
||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||||
|
"//submodules/Svg:Svg",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import TinyThumbnail
|
|||||||
import ImageTransparency
|
import ImageTransparency
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import MusicAlbumArtResources
|
import MusicAlbumArtResources
|
||||||
|
import Svg
|
||||||
|
|
||||||
private enum ResourceFileData {
|
private enum ResourceFileData {
|
||||||
case data(Data)
|
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> {
|
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 }) {
|
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 }) {
|
||||||
|
|
||||||
|
|||||||
@ -292,6 +292,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1885586395] = { return Api.Update.parse_updatePendingJoinRequests($0) }
|
dict[1885586395] = { return Api.Update.parse_updatePendingJoinRequests($0) }
|
||||||
dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) }
|
dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) }
|
||||||
dict[357013699] = { return Api.Update.parse_updateMessageReactions($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[1951948721] = { return Api.Update.parse_updateReadFeed($0) }
|
||||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($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[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
|
||||||
dict[-376962181] = { return Api.KeyboardButton.parse_inputKeyboardButtonUserProfile($0) }
|
dict[-376962181] = { return Api.KeyboardButton.parse_inputKeyboardButtonUserProfile($0) }
|
||||||
dict[814112961] = { return Api.KeyboardButton.parse_keyboardButtonUserProfile($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[383348795] = { return Api.ContactStatus.parse_contactStatus($0) }
|
||||||
dict[997004590] = { return Api.users.UserFull.parse_userFull($0) }
|
dict[997004590] = { return Api.users.UserFull.parse_userFull($0) }
|
||||||
dict[1679398724] = { return Api.SecureFile.parse_secureFileEmpty($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[-1798033689] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilterEmpty($0) }
|
||||||
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
|
dict[-847783593] = { return Api.ChannelMessagesFilter.parse_channelMessagesFilter($0) }
|
||||||
dict[-219353309] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($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[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
|
||||||
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
|
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
|
||||||
dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($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[341499403] = { return Api.Contact.parse_contact($0) }
|
||||||
dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) }
|
dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) }
|
||||||
dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($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[1648543603] = { return Api.FileHash.parse_fileHash($0) }
|
||||||
dict[295067450] = { return Api.BotInlineResult.parse_botInlineResult($0) }
|
dict[295067450] = { return Api.BotInlineResult.parse_botInlineResult($0) }
|
||||||
dict[400266251] = { return Api.BotInlineResult.parse_botInlineMediaResult($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[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) }
|
||||||
dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) }
|
dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) }
|
||||||
dict[1336154098] = { return Api.InputBotInlineResult.parse_inputBotInlineResultGame($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[1352683077] = { return Api.account.PrivacyRules.parse_privacyRules($0) }
|
||||||
dict[-123988] = { return Api.PrivacyRule.parse_privacyValueAllowContacts($0) }
|
dict[-123988] = { return Api.PrivacyRule.parse_privacyValueAllowContacts($0) }
|
||||||
dict[1698855810] = { return Api.PrivacyRule.parse_privacyValueAllowAll($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[512177195] = { return Api.Document.parse_document($0) }
|
||||||
dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) }
|
dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) }
|
||||||
dict[-1493633966] = { return Api.WebAuthorization.parse_webAuthorization($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[-1052885936] = { return Api.ImportedContact.parse_importedContact($0) }
|
||||||
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
||||||
return dict
|
return dict
|
||||||
@ -1223,6 +1231,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.ChatAdminWithInvites:
|
case let _1 as Api.ChatAdminWithInvites:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.AttachMenuBot:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.DialogFilterSuggested:
|
case let _1 as Api.DialogFilterSuggested:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.auth.PasswordRecovery:
|
case let _1 as Api.auth.PasswordRecovery:
|
||||||
@ -1247,6 +1257,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.help.PassportConfig:
|
case let _1 as Api.help.PassportConfig:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.WebViewResult:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.FileHash:
|
case let _1 as Api.FileHash:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.BotInlineResult:
|
case let _1 as Api.BotInlineResult:
|
||||||
@ -1629,6 +1641,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.InputBotInlineResult:
|
case let _1 as Api.InputBotInlineResult:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.AttachMenuBots:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.account.PrivacyRules:
|
case let _1 as Api.account.PrivacyRules:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.PrivacyRule:
|
case let _1 as Api.PrivacyRule:
|
||||||
@ -1699,6 +1713,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.WebAuthorization:
|
case let _1 as Api.WebAuthorization:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.messages.WebViewResult:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.ImportedContact:
|
case let _1 as Api.ImportedContact:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.payments.BankCardData:
|
case let _1 as Api.payments.BankCardData:
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4936,6 +4936,7 @@ public extension Api {
|
|||||||
case updatePendingJoinRequests(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64])
|
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 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 updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
|
||||||
|
case updateAttachMenuBots
|
||||||
case updateReadFeed(flags: Int32, filterId: Int32, maxPosition: Api.FeedPosition, unreadCount: Int32?, unreadMutedCount: Int32?)
|
case updateReadFeed(flags: Int32, filterId: Int32, maxPosition: Api.FeedPosition, unreadCount: Int32?, unreadMutedCount: Int32?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
@ -5781,6 +5782,12 @@ public extension Api {
|
|||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
reactions.serialize(buffer, true)
|
reactions.serialize(buffer, true)
|
||||||
|
break
|
||||||
|
case .updateAttachMenuBots:
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(397910539)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
|
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -5989,6 +5996,8 @@ public extension Api {
|
|||||||
return ("updateBotChatInviteRequester", [("peer", peer), ("date", date), ("userId", userId), ("about", about), ("invite", invite), ("qts", qts)])
|
return ("updateBotChatInviteRequester", [("peer", peer), ("date", date), ("userId", userId), ("about", about), ("invite", invite), ("qts", qts)])
|
||||||
case .updateMessageReactions(let peer, let msgId, let reactions):
|
case .updateMessageReactions(let peer, let msgId, let reactions):
|
||||||
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", 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):
|
case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount):
|
||||||
return ("updateReadFeed", [("flags", flags), ("filterId", filterId), ("maxPosition", maxPosition), ("unreadCount", unreadCount), ("unreadMutedCount", unreadMutedCount)])
|
return ("updateReadFeed", [("flags", flags), ("filterId", filterId), ("maxPosition", maxPosition), ("unreadCount", unreadCount), ("unreadMutedCount", unreadMutedCount)])
|
||||||
}
|
}
|
||||||
@ -7722,6 +7731,9 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? {
|
||||||
|
return Api.Update.updateAttachMenuBots
|
||||||
|
}
|
||||||
public static func parse_updateReadFeed(_ reader: BufferReader) -> Update? {
|
public static func parse_updateReadFeed(_ reader: BufferReader) -> Update? {
|
||||||
var _1: Int32?
|
var _1: Int32?
|
||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
@ -8253,6 +8265,7 @@ public extension Api {
|
|||||||
case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String)
|
case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String)
|
||||||
case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser)
|
case inputKeyboardButtonUserProfile(text: String, userId: Api.InputUser)
|
||||||
case keyboardButtonUserProfile(text: String, userId: Int64)
|
case keyboardButtonUserProfile(text: String, userId: Int64)
|
||||||
|
case keyboardButtonWebView(text: String, url: String)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -8351,6 +8364,13 @@ public extension Api {
|
|||||||
serializeString(text, buffer: buffer, boxed: false)
|
serializeString(text, buffer: buffer, boxed: false)
|
||||||
serializeInt64(userId, buffer: buffer, boxed: false)
|
serializeInt64(userId, buffer: buffer, boxed: false)
|
||||||
break
|
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)])
|
return ("inputKeyboardButtonUserProfile", [("text", text), ("userId", userId)])
|
||||||
case .keyboardButtonUserProfile(let text, let userId):
|
case .keyboardButtonUserProfile(let text, let userId):
|
||||||
return ("keyboardButtonUserProfile", [("text", text), ("userId", 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
|
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 {
|
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 {
|
public enum DialogFilterSuggested: TypeConstructorDescription {
|
||||||
case dialogFilterSuggested(filter: Api.DialogFilter, description: String)
|
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 {
|
public enum FileHash: TypeConstructorDescription {
|
||||||
case fileHash(offset: Int32, limit: Int32, hash: Buffer)
|
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 {
|
public enum PrivacyRule: TypeConstructorDescription {
|
||||||
case privacyValueAllowContacts
|
case privacyValueAllowContacts
|
||||||
|
|||||||
@ -4849,6 +4849,84 @@ public extension Api {
|
|||||||
return result
|
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 struct channels {
|
||||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
|||||||
@ -1091,6 +1091,7 @@ public class Account {
|
|||||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
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(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(managedSynchronizeAvailableReactions(postbox: self.postbox, network: self.network).start())
|
||||||
|
self.managedOperationsDisposable.add(managedSynchronizeAttachMenuBots(postbox: self.postbox, network: self.network).start())
|
||||||
|
|
||||||
if !supplementary {
|
if !supplementary {
|
||||||
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start())
|
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start())
|
||||||
|
|||||||
@ -112,6 +112,7 @@ enum AccountStateMutationOperation {
|
|||||||
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
|
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
|
||||||
case UpdateGroupCall(peerId: PeerId, call: Api.GroupCall)
|
case UpdateGroupCall(peerId: PeerId, call: Api.GroupCall)
|
||||||
case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?)
|
case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?)
|
||||||
|
case UpdateAttachMenuBots
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HoleFromPreviousState {
|
struct HoleFromPreviousState {
|
||||||
@ -496,9 +497,13 @@ struct AccountMutableState {
|
|||||||
self.addOperation(.UpdateChatListFilter(id: id, filter: filter))
|
self.addOperation(.UpdateChatListFilter(id: id, filter: filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func addUpdateAttachMenuBots() {
|
||||||
|
self.addOperation(.UpdateAttachMenuBots)
|
||||||
|
}
|
||||||
|
|
||||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
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
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
|||||||
@ -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))))
|
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))))
|
||||||
case let .inputKeyboardButtonUserProfile(text, _):
|
case let .inputKeyboardButtonUserProfile(text, _):
|
||||||
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,9 @@ extension TelegramUser {
|
|||||||
if (flags & (1 << 21)) != 0 {
|
if (flags & (1 << 21)) != 0 {
|
||||||
botFlags.insert(.requiresGeolocationForInlineRequests)
|
botFlags.insert(.requiresGeolocationForInlineRequests)
|
||||||
}
|
}
|
||||||
|
if (flags & (1 << 27)) != 0 {
|
||||||
|
botFlags.insert(.canBeAddedToAttachMenu)
|
||||||
|
}
|
||||||
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
|
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +121,9 @@ extension TelegramUser {
|
|||||||
if (flags & (1 << 21)) != 0 {
|
if (flags & (1 << 21)) != 0 {
|
||||||
botFlags.insert(.requiresGeolocationForInlineRequests)
|
botFlags.insert(.requiresGeolocationForInlineRequests)
|
||||||
}
|
}
|
||||||
|
if (flags & (1 << 27)) != 0 {
|
||||||
|
botFlags.insert(.canBeAddedToAttachMenu)
|
||||||
|
}
|
||||||
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
|
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1512,6 +1512,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
})
|
})
|
||||||
case let .updateMessageReactions(peer, msgId, reactions):
|
case let .updateMessageReactions(peer, msgId, reactions):
|
||||||
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate)
|
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate)
|
||||||
|
case .updateAttachMenuBots:
|
||||||
|
updatedState.addUpdateAttachMenuBots()
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -2304,7 +2306,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
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 {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -2417,6 +2419,7 @@ func replayFinalState(
|
|||||||
var peerActivityTimestamps: [PeerId: Int32] = [:]
|
var peerActivityTimestamps: [PeerId: Int32] = [:]
|
||||||
var syncChatListFilters = false
|
var syncChatListFilters = false
|
||||||
var deletedMessageIds: [DeletedMessageId] = []
|
var deletedMessageIds: [DeletedMessageId] = []
|
||||||
|
var syncAttachMenuBots = false
|
||||||
|
|
||||||
var holesFromPreviousStateMessageIds: [MessageId] = []
|
var holesFromPreviousStateMessageIds: [MessageId] = []
|
||||||
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
|
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))
|
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 {
|
for groupId in invalidateGroupStats {
|
||||||
transaction.setNeedsPeerGroupMessageStatsSynchronization(groupId: groupId, namespace: Namespaces.Message.Cloud)
|
transaction.setNeedsPeerGroupMessageStatsSynchronization(groupId: groupId, namespace: Namespaces.Message.Cloud)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,6 +83,7 @@ public struct Namespaces {
|
|||||||
public static let cachedSendAsPeers: Int8 = 18
|
public static let cachedSendAsPeers: Int8 = 18
|
||||||
public static let availableReactions: Int8 = 19
|
public static let availableReactions: Int8 = 19
|
||||||
public static let resolvedByPhonePeers: Int8 = 20
|
public static let resolvedByPhonePeers: Int8 = 20
|
||||||
|
public static let attachMenuBots: Int8 = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct UnorderedItemList {
|
public struct UnorderedItemList {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
|||||||
case urlAuth(url: String, buttonId: Int32)
|
case urlAuth(url: String, buttonId: Int32)
|
||||||
case setupPoll(isQuiz: Bool?)
|
case setupPoll(isQuiz: Bool?)
|
||||||
case openUserProfile(peerId: PeerId)
|
case openUserProfile(peerId: PeerId)
|
||||||
|
case openWebView(url: String)
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
switch decoder.decodeInt32ForKey("v", orElse: 0) {
|
||||||
@ -37,6 +38,8 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
|||||||
self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 })
|
self = .setupPoll(isQuiz: decoder.decodeOptionalInt32ForKey("isq").flatMap { $0 != 0 })
|
||||||
case 10:
|
case 10:
|
||||||
self = .openUserProfile(peerId: PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0)))
|
self = .openUserProfile(peerId: PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0)))
|
||||||
|
case 11:
|
||||||
|
self = .openWebView(url: decoder.decodeStringForKey("u", orElse: ""))
|
||||||
default:
|
default:
|
||||||
self = .text
|
self = .text
|
||||||
}
|
}
|
||||||
@ -79,6 +82,9 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
|
|||||||
case let .openUserProfile(peerId):
|
case let .openUserProfile(peerId):
|
||||||
encoder.encodeInt32(10, forKey: "v")
|
encoder.encodeInt32(10, forKey: "v")
|
||||||
encoder.encodeInt64(peerId.toInt64(), forKey: "peerId")
|
encoder.encodeInt64(peerId.toInt64(), forKey: "peerId")
|
||||||
|
case let .openWebView(url):
|
||||||
|
encoder.encodeInt32(11, forKey: "v")
|
||||||
|
encoder.encodeString(url, forKey: "u")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ public struct BotUserInfoFlags: OptionSet {
|
|||||||
public static let hasAccessToChatHistory = BotUserInfoFlags(rawValue: (1 << 0))
|
public static let hasAccessToChatHistory = BotUserInfoFlags(rawValue: (1 << 0))
|
||||||
public static let worksWithGroups = BotUserInfoFlags(rawValue: (1 << 1))
|
public static let worksWithGroups = BotUserInfoFlags(rawValue: (1 << 1))
|
||||||
public static let requiresGeolocationForInlineRequests = BotUserInfoFlags(rawValue: (1 << 3))
|
public static let requiresGeolocationForInlineRequests = BotUserInfoFlags(rawValue: (1 << 3))
|
||||||
|
public static let canBeAddedToAttachMenu = BotUserInfoFlags(rawValue: (1 << 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct BotUserInfo: PostboxCoding, Equatable {
|
public struct BotUserInfo: PostboxCoding, Equatable {
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -2,19 +2,19 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
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 {
|
func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> Bool {
|
||||||
guard let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else {
|
guard let message = _internal_outgoingMessageWithChatContextResult(to: peerId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start()
|
let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start()
|
||||||
return true
|
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] = []
|
var attributes: [MessageAttribute] = []
|
||||||
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: result.queryId, id: result.id, hideVia: hideVia))
|
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: result.queryId, id: result.id, hideVia: hideVia))
|
||||||
if !hideVia {
|
if !hideVia {
|
||||||
attributes.append(InlineBotMessageAttribute(peerId: results.botId, title: nil))
|
attributes.append(InlineBotMessageAttribute(peerId: botId, title: nil))
|
||||||
}
|
}
|
||||||
if let scheduleTime = scheduleTime {
|
if let scheduleTime = scheduleTime {
|
||||||
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
|
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
|
||||||
|
|||||||
@ -160,8 +160,12 @@ public extension TelegramEngine {
|
|||||||
return _internal_exportMessageLink(account: self.account, peerId: peerId, messageId: messageId, isThread: isThread)
|
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 {
|
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, results: results, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId)
|
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> {
|
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> {
|
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)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 |
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "attachalert_48.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
163
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/attachalert_48.pdf
vendored
Normal file
163
submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/BotPlus.imageset/attachalert_48.pdf
vendored
Normal 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
|
||||||
@ -215,6 +215,8 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
|
|||||||
self.controllerInteraction.openPollCreation(isQuiz)
|
self.controllerInteraction.openPollCreation(isQuiz)
|
||||||
case let .openUserProfile(peerId):
|
case let .openUserProfile(peerId):
|
||||||
self.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
self.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
||||||
|
case let .openWebView(url):
|
||||||
|
self.controllerInteraction.openWebView(url)
|
||||||
}
|
}
|
||||||
if dismissIfOnce {
|
if dismissIfOnce {
|
||||||
if let message = self.message {
|
if let message = self.message {
|
||||||
|
|||||||
@ -219,6 +219,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
public let chatLocation: ChatLocation
|
public let chatLocation: ChatLocation
|
||||||
public let subject: ChatControllerSubject?
|
public let subject: ChatControllerSubject?
|
||||||
private let botStart: ChatControllerInitialBotStart?
|
private let botStart: ChatControllerInitialBotStart?
|
||||||
|
private var attachBotId: PeerId?
|
||||||
|
|
||||||
private let peerDisposable = MetaDisposable()
|
private let peerDisposable = MetaDisposable()
|
||||||
private let titleDisposable = MetaDisposable()
|
private let titleDisposable = MetaDisposable()
|
||||||
@ -499,7 +500,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private var inviteRequestsContext: PeerInvitationImportersContext?
|
private var inviteRequestsContext: PeerInvitationImportersContext?
|
||||||
private var inviteRequestsDisposable = MetaDisposable()
|
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
|
let _ = ChatControllerCount.modify { value in
|
||||||
return value + 1
|
return value + 1
|
||||||
}
|
}
|
||||||
@ -509,6 +510,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.chatLocationContextHolder = chatLocationContextHolder
|
self.chatLocationContextHolder = chatLocationContextHolder
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.botStart = botStart
|
self.botStart = botStart
|
||||||
|
self.attachBotId = attachBotId
|
||||||
self.peekData = peekData
|
self.peekData = peekData
|
||||||
self.currentChatListFilter = chatListFilter
|
self.currentChatListFilter = chatListFilter
|
||||||
self.chatNavigationStack = chatNavigationStack
|
self.chatNavigationStack = chatNavigationStack
|
||||||
@ -3299,6 +3301,77 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil)
|
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
|
}, requestMessageUpdate: { [weak self] id in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||||
@ -9043,6 +9116,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
} else {
|
} else {
|
||||||
self.chatDisplayNode.historyNode.preloadPages = true
|
self.chatDisplayNode.historyNode.preloadPages = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let attachBotId = self.attachBotId {
|
||||||
|
self.attachBotId = nil
|
||||||
|
self.presentAttachmentBot(botId: attachBotId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewWillDisappear(_ animated: Bool) {
|
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 {
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
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 {
|
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
|
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 currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||||
let currentLocationController = 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
|
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -10684,8 +10803,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let controller = strongSelf.configurePollCreation()
|
let controller = strongSelf.configurePollCreation()
|
||||||
completion(controller, nil)
|
completion(controller, nil)
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
case .app:
|
case let .app(botId, botName, _):
|
||||||
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, url: "", message: nil)
|
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, botId: botId, botName: botName, url: nil, queryId: nil)
|
||||||
completion(controller, nil)
|
completion(controller, nil)
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
}
|
}
|
||||||
@ -12222,7 +12341,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId
|
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
|
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
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, {
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||||
$0.updatedBotStartPayload(botStart.payload)
|
$0.updatedBotStartPayload(botStart.payload)
|
||||||
})
|
})
|
||||||
|
case .withAttachBot:
|
||||||
|
self.presentAttachmentMenu(editMediaOptions: nil, editMediaReference: nil)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -13747,6 +13868,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
case let .withBotStartPayload(botStart):
|
case let .withBotStartPayload(botStart):
|
||||||
self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId), botStart: 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 {
|
} else {
|
||||||
@ -14171,7 +14296,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?) {
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -14204,6 +14332,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
} else if let navigationController = strongSelf.effectiveNavigationController {
|
} else if let navigationController = strongSelf.effectiveNavigationController {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload))
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,6 +131,7 @@ public final class ChatControllerInteraction {
|
|||||||
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
|
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
|
||||||
let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void
|
let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void
|
||||||
let openJoinLink: (String) -> Void
|
let openJoinLink: (String) -> Void
|
||||||
|
let openWebView: (String) -> Void
|
||||||
|
|
||||||
let requestMessageUpdate: (MessageId) -> Void
|
let requestMessageUpdate: (MessageId) -> Void
|
||||||
let cancelInteractiveKeyboardGestures: () -> Void
|
let cancelInteractiveKeyboardGestures: () -> Void
|
||||||
@ -230,6 +231,7 @@ public final class ChatControllerInteraction {
|
|||||||
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
|
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
|
||||||
openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void,
|
openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void,
|
||||||
openJoinLink: @escaping (String) -> Void,
|
openJoinLink: @escaping (String) -> Void,
|
||||||
|
openWebView: @escaping (String) -> Void,
|
||||||
requestMessageUpdate: @escaping (MessageId) -> Void,
|
requestMessageUpdate: @escaping (MessageId) -> Void,
|
||||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||||
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
||||||
@ -315,6 +317,7 @@ public final class ChatControllerInteraction {
|
|||||||
self.commitEmojiInteraction = commitEmojiInteraction
|
self.commitEmojiInteraction = commitEmojiInteraction
|
||||||
self.openLargeEmojiInfo = openLargeEmojiInfo
|
self.openLargeEmojiInfo = openLargeEmojiInfo
|
||||||
self.openJoinLink = openJoinLink
|
self.openJoinLink = openJoinLink
|
||||||
|
self.openWebView = openWebView
|
||||||
self.requestMessageUpdate = requestMessageUpdate
|
self.requestMessageUpdate = requestMessageUpdate
|
||||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||||
|
|
||||||
@ -374,6 +377,7 @@ public final class ChatControllerInteraction {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
|
}, openWebView: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
|||||||
@ -2568,13 +2568,25 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.historyNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false)
|
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()
|
UIGraphicsPopContext()
|
||||||
}).flatMap(applyScreenshotEffectToImage)
|
}).flatMap(applyScreenshotEffectToImage)
|
||||||
let blurredHistoryNode = ASImageNode()
|
let blurredHistoryNode = ASImageNode()
|
||||||
blurredHistoryNode.image = image
|
blurredHistoryNode.image = image
|
||||||
blurredHistoryNode.frame = self.historyNode.frame
|
blurredHistoryNode.frame = self.historyNode.frame
|
||||||
self.blurredHistoryNode = blurredHistoryNode
|
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 {
|
} else {
|
||||||
if let blurredHistoryNode = self.blurredHistoryNode {
|
if let blurredHistoryNode = self.blurredHistoryNode {
|
||||||
|
|||||||
@ -112,7 +112,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
|
||||||
case .openUserProfile:
|
case .openUserProfile:
|
||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage
|
||||||
case .openWebApp:
|
case .openWebView:
|
||||||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingWebAppIconImage : graphics.chatBubbleActionButtonOutgoingWebAppIconImage
|
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingWebAppIconImage : graphics.chatBubbleActionButtonOutgoingWebAppIconImage
|
||||||
default:
|
default:
|
||||||
iconImage = nil
|
iconImage = nil
|
||||||
|
|||||||
@ -854,6 +854,8 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
|
|||||||
break
|
break
|
||||||
case let .openUserProfile(peerId):
|
case let .openUserProfile(peerId):
|
||||||
item.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
item.controllerInteraction.openPeer(peerId, .info, nil, nil)
|
||||||
|
case let .openWebView(url):
|
||||||
|
item.controllerInteraction.openWebView(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -532,6 +532,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
|
}, openWebView: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||||
@ -937,6 +938,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}), .window(.root), nil)
|
}), .window(.root), nil)
|
||||||
case .importStickers:
|
case .importStickers:
|
||||||
break
|
break
|
||||||
|
case .setAttach:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -159,6 +159,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
|
}, openWebView: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
|||||||
@ -61,6 +61,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
return state.updatedBotStartPayload(botStart.payload)
|
return state.updatedBotStartPayload(botStart.payload)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if let botId = params.attachBotId {
|
||||||
|
controller.presentAttachmentBot(botId: botId)
|
||||||
|
}
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -76,8 +79,11 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
return state.updatedBotStartPayload(botStart.payload)
|
return state.updatedBotStartPayload(botStart.payload)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if let botId = params.attachBotId {
|
||||||
|
controller.presentAttachmentBot(botId: botId)
|
||||||
|
}
|
||||||
} else {
|
} 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
|
controller.purposefulAction = params.purposefulAction
|
||||||
if let search = params.activateMessageSearch {
|
if let search = params.activateMessageSearch {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import UndoUI
|
|||||||
import ImportStickerPackUI
|
import ImportStickerPackUI
|
||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import WebUI
|
||||||
|
|
||||||
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
||||||
if case .default = navigation {
|
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?) {
|
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>)?
|
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
if case let .chat(maybeUpdatedPresentationData) = urlContext {
|
if case let .chat(_, maybeUpdatedPresentationData) = urlContext {
|
||||||
updatedPresentationData = maybeUpdatedPresentationData
|
updatedPresentationData = maybeUpdatedPresentationData
|
||||||
} else {
|
} else {
|
||||||
updatedPresentationData = nil
|
updatedPresentationData = nil
|
||||||
@ -546,5 +547,43 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
present(controller, nil)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -208,6 +208,11 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
if let navigationController = navigationController {
|
if let navigationController = navigationController {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), botStart: payload))
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -614,6 +619,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
var game: String?
|
var game: String?
|
||||||
var post: String?
|
var post: String?
|
||||||
var voiceChat: String?
|
var voiceChat: String?
|
||||||
|
var attach: String?
|
||||||
|
var setAttach: String?
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
@ -633,9 +640,13 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
post = value
|
post = value
|
||||||
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
||||||
voiceChat = value
|
voiceChat = value
|
||||||
|
} else if queryItem.name == "attach" {
|
||||||
|
attach = value
|
||||||
}
|
}
|
||||||
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
||||||
voiceChat = ""
|
voiceChat = ""
|
||||||
|
} else if queryItem.name == "setattach" {
|
||||||
|
setAttach = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -662,6 +673,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
} else {
|
} else {
|
||||||
result += "?voicechat="
|
result += "?voicechat="
|
||||||
}
|
}
|
||||||
|
} else if let attach = attach {
|
||||||
|
result += "?attach=\(attach)"
|
||||||
|
} else if let _ = setAttach {
|
||||||
|
result += "?setattach"
|
||||||
}
|
}
|
||||||
convertedUrl = result
|
convertedUrl = result
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,6 +151,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
|
}, openWebView: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))
|
||||||
|
|||||||
@ -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) {
|
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()
|
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 {
|
} else if let channel = data.peer as? TelegramChannel {
|
||||||
let ItemUsername = 1
|
let ItemUsername = 1
|
||||||
@ -2265,6 +2266,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
|
}, openWebView: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
@ -3284,7 +3286,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||||
return
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3303,6 +3305,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}))
|
}))
|
||||||
case let .withBotStartPayload(startPayload):
|
case let .withBotStartPayload(startPayload):
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: 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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -3379,6 +3383,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
if let navigationController = self.controller?.navigationController as? NavigationController {
|
if let navigationController = self.controller?.navigationController as? NavigationController {
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peerId), botStart: startPayload))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1325,6 +1325,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}, commitEmojiInteraction: { _, _, _, _ in
|
}, commitEmojiInteraction: { _, _, _, _ in
|
||||||
}, openLargeEmojiInfo: { _, _, _ in
|
}, openLargeEmojiInfo: { _, _, _ in
|
||||||
}, openJoinLink: { _ in
|
}, openJoinLink: { _ in
|
||||||
|
}, openWebView: { _ in
|
||||||
}, requestMessageUpdate: { _ in
|
}, requestMessageUpdate: { _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||||
|
|||||||
@ -66,6 +66,7 @@ extension ResolvedBotAdminRights {
|
|||||||
public enum ParsedInternalPeerUrlParameter {
|
public enum ParsedInternalPeerUrlParameter {
|
||||||
case botStart(String)
|
case botStart(String)
|
||||||
case groupBotStart(String, ResolvedBotAdminRights?)
|
case groupBotStart(String, ResolvedBotAdminRights?)
|
||||||
|
case attachBotStart(String)
|
||||||
case channelMessage(Int32, Double?)
|
case channelMessage(Int32, Double?)
|
||||||
case replyThread(Int32, Int32)
|
case replyThread(Int32, Int32)
|
||||||
case voiceChat(String?)
|
case voiceChat(String?)
|
||||||
@ -86,6 +87,7 @@ public enum ParsedInternalUrl {
|
|||||||
case wallpaper(WallpaperUrlParameter)
|
case wallpaper(WallpaperUrlParameter)
|
||||||
case theme(String)
|
case theme(String)
|
||||||
case phone(String)
|
case phone(String)
|
||||||
|
case setAttach(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ParsedUrl {
|
private enum ParsedUrl {
|
||||||
@ -182,7 +184,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
} else {
|
} else {
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
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))
|
return .peerName(peerName, .botStart(value))
|
||||||
} else if queryItem.name == "startgroup" {
|
} else if queryItem.name == "startgroup" {
|
||||||
var botAdminRights: ResolvedBotAdminRights?
|
var botAdminRights: ResolvedBotAdminRights?
|
||||||
@ -200,6 +204,8 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
}
|
}
|
||||||
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
|
||||||
return .peerName(peerName, .voiceChat(nil))
|
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))
|
return .single(.botStart(peerId: peer.id, payload: payload))
|
||||||
case let .groupBotStart(payload, adminRights):
|
case let .groupBotStart(payload, adminRights):
|
||||||
return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: 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):
|
case let .channelMessage(id, timecode):
|
||||||
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: 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):
|
case let .replyThread(id, replyId):
|
||||||
@ -523,6 +542,19 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
return .single(.wallpaper(parameter))
|
return .single(.wallpaper(parameter))
|
||||||
case let .theme(slug):
|
case let .theme(slug):
|
||||||
return .single(.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,9 +15,12 @@ swift_library(
|
|||||||
"//submodules/Display:Display",
|
"//submodules/Display:Display",
|
||||||
"//submodules/TelegramCore:TelegramCore",
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/AttachmentUI:AttachmentUI",
|
"//submodules/AttachmentUI:AttachmentUI",
|
||||||
"//submodules/CounterContollerTitleView:CounterContollerTitleView",
|
"//submodules/CounterContollerTitleView:CounterContollerTitleView",
|
||||||
|
"//submodules/HexColor:HexColor",
|
||||||
|
"//submodules/PhotoResources:PhotoResources",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -9,11 +9,15 @@ import TelegramPresentationData
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import PhotoResources
|
||||||
|
|
||||||
private final class WebAppAlertContentNode: AlertContentNode {
|
private final class WebAppAlertContentNode: AlertContentNode {
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
private let peerName: String
|
||||||
|
private let peerIcon: TelegramMediaFile
|
||||||
|
|
||||||
private let textNode: ASTextNode
|
private let textNode: ASTextNode
|
||||||
|
private let appIconNode: ASImageNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
|
|
||||||
private let actionNodesSeparator: ASDisplayNode
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
@ -22,16 +26,24 @@ private final class WebAppAlertContentNode: AlertContentNode {
|
|||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
private var iconDisposable: Disposable?
|
||||||
|
|
||||||
override var dismissOnOutsideTap: Bool {
|
override var dismissOnOutsideTap: Bool {
|
||||||
return self.isUserInteractionEnabled
|
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.strings = strings
|
||||||
|
self.peerName = peerName
|
||||||
|
self.peerIcon = peerIcon
|
||||||
|
|
||||||
self.textNode = ASTextNode()
|
self.textNode = ASTextNode()
|
||||||
self.textNode.maximumNumberOfLines = 0
|
self.textNode.maximumNumberOfLines = 0
|
||||||
|
|
||||||
|
self.appIconNode = ASImageNode()
|
||||||
|
self.appIconNode.displaysAsynchronously = false
|
||||||
|
self.appIconNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
self.iconNode.displayWithoutProcessing = true
|
self.iconNode.displayWithoutProcessing = true
|
||||||
@ -56,6 +68,7 @@ private final class WebAppAlertContentNode: AlertContentNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.appIconNode)
|
||||||
self.addSubnode(self.iconNode)
|
self.addSubnode(self.iconNode)
|
||||||
|
|
||||||
self.addSubnode(self.actionNodesSeparator)
|
self.addSubnode(self.actionNodesSeparator)
|
||||||
@ -69,11 +82,24 @@ private final class WebAppAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.updateTheme(theme)
|
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) {
|
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.textNode.attributedText = NSAttributedString(string: strings.WebApp_AddToAttachmentText(self.peerName).string, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Bot Payments/BotLogo"), color: theme.accentColor)
|
|
||||||
|
self.appIconNode.image = generateTintedImage(image: self.appIconNode.image, color: theme.accentColor)
|
||||||
|
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/BotPlus"), color: theme.accentColor)
|
||||||
|
|
||||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||||
for actionNode in self.actionNodes {
|
for actionNode in self.actionNodes {
|
||||||
@ -187,7 +213,9 @@ private final class WebAppAlertContentNode: AlertContentNode {
|
|||||||
nodeIndex += 1
|
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)
|
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||||
|
|
||||||
textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0)
|
textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0)
|
||||||
@ -197,8 +225,8 @@ private final class WebAppAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addWebAppToAttachmentController(sharedContext: SharedAccountContext) -> AlertController {
|
public func addWebAppToAttachmentController(context: AccountContext, peerName: String, peerIcon: TelegramMediaFile, completion: @escaping () -> Void) -> AlertController {
|
||||||
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let theme = presentationData.theme
|
let theme = presentationData.theme
|
||||||
let strings = presentationData.strings
|
let strings = presentationData.strings
|
||||||
|
|
||||||
@ -209,9 +237,10 @@ func addWebAppToAttachmentController(sharedContext: SharedAccountContext) -> Ale
|
|||||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: {
|
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: {
|
||||||
dismissImpl?(true)
|
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!)
|
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
||||||
dismissImpl = { [weak controller] animated in
|
dismissImpl = { [weak controller] animated in
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import UIKit
|
|||||||
import WebKit
|
import WebKit
|
||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
@ -10,6 +11,8 @@ import AccountContext
|
|||||||
import AttachmentUI
|
import AttachmentUI
|
||||||
import CounterContollerTitleView
|
import CounterContollerTitleView
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import PresentationDataUtils
|
||||||
|
import HexColor
|
||||||
|
|
||||||
private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler {
|
private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler {
|
||||||
private let f: (WKScriptMessage) -> ()
|
private let f: (WKScriptMessage) -> ()
|
||||||
@ -25,29 +28,53 @@ 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 final class WebAppController: ViewController, AttachmentContainable {
|
||||||
public var requestAttachmentMenuExpansion: () -> Void = { }
|
public var requestAttachmentMenuExpansion: () -> Void = { }
|
||||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||||
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
||||||
public var cancelPanGesture: () -> Void = { }
|
public var cancelPanGesture: () -> Void = { }
|
||||||
|
|
||||||
private class Node: ViewControllerTracingNode {
|
private class Node: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||||
|
private weak var controller: WebAppController?
|
||||||
|
|
||||||
private var webView: WKWebView?
|
private var webView: WKWebView?
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
var presentationData: PresentationData
|
var presentationData: PresentationData
|
||||||
private let present: (ViewController, Any?) -> Void
|
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.context = context
|
||||||
|
self.controller = controller
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.present = present
|
self.present = present
|
||||||
self.message = message
|
|
||||||
|
|
||||||
super.init()
|
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() {}; " +
|
let js = "var TelegramWebviewProxyProto = function() {}; " +
|
||||||
"TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " +
|
"TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " +
|
||||||
@ -55,18 +82,21 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
"}; " +
|
"}; " +
|
||||||
"var TelegramWebviewProxy = new TelegramWebviewProxyProto();"
|
"var TelegramWebviewProxy = new TelegramWebviewProxyProto();"
|
||||||
|
|
||||||
let configuration = WKWebViewConfiguration()
|
|
||||||
let userController = WKUserContentController()
|
|
||||||
|
|
||||||
let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||||
userController.addUserScript(userScript)
|
userController.addUserScript(userScript)
|
||||||
|
|
||||||
userController.add(WeakGameScriptMessageHandler { [weak self] message in
|
userController.add(WeakGameScriptMessageHandler { [weak self] message in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.handleScriptMessage(message)
|
strongSelf.handleScriptMessage(message)
|
||||||
}
|
}
|
||||||
}, name: "performAction")
|
}, 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.userContentController = userController
|
||||||
|
|
||||||
configuration.allowsInlineMediaPlayback = true
|
configuration.allowsInlineMediaPlayback = true
|
||||||
@ -79,6 +109,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let webView = WKWebView(frame: CGRect(), configuration: configuration)
|
let webView = WKWebView(frame: CGRect(), configuration: configuration)
|
||||||
|
webView.isOpaque = false
|
||||||
|
webView.backgroundColor = .clear
|
||||||
|
|
||||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||||
webView.allowsLinkPreview = false
|
webView.allowsLinkPreview = false
|
||||||
}
|
}
|
||||||
@ -88,18 +121,62 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in
|
webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in
|
||||||
return point.x > 30.0
|
return point.x > 30.0
|
||||||
}
|
}
|
||||||
|
webView.allowsBackForwardNavigationGestures = false
|
||||||
self.view.addSubview(webView)
|
webView.scrollView.delegate = self
|
||||||
self.webView = webView
|
self.webView = webView
|
||||||
|
|
||||||
if let parsedUrl = URL(string: url) {
|
if let url = url, let queryId = queryId {
|
||||||
webView.load(URLRequest(url: parsedUrl))
|
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) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
if let webView = self.webView {
|
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) {
|
private func handleScriptMessage(_ message: WKScriptMessage) {
|
||||||
guard let body = message.body as? [String: Any] else {
|
guard let body = message.body as? [String: Any] else {
|
||||||
return
|
return
|
||||||
@ -150,15 +199,67 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if eventName == "share_game" || eventName == "share_score" {
|
switch eventName {
|
||||||
if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
|
case "webview_send_result_message":
|
||||||
if eventName == "share_score" {
|
self.handleSendResultMessage()
|
||||||
|
case "webview_close":
|
||||||
|
self.controller?.dismiss()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
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
|
return self.displayNode as! Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var titleView: CounterContollerTitleView?
|
||||||
private let moreButtonNode: MoreButtonNode
|
private let moreButtonNode: MoreButtonNode
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let url: String
|
private let peerId: PeerId
|
||||||
private let message: EngineMessage?
|
private let botId: PeerId
|
||||||
|
private let url: String?
|
||||||
|
private let queryId: Int64?
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
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.context = context
|
||||||
|
self.peerId = peerId
|
||||||
|
self.botId = botId
|
||||||
self.url = url
|
self.url = url
|
||||||
self.message = message
|
self.queryId = queryId
|
||||||
|
|
||||||
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var theme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
|
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: ""))
|
let navigationBarPresentationData = NavigationBarPresentationData(theme: theme, strings: NavigationBarStrings(back: "", close: ""))
|
||||||
|
|
||||||
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
|
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
|
||||||
@ -199,14 +312,34 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.navigationItem.rightBarButtonItem?.target = self
|
self.navigationItem.rightBarButtonItem?.target = self
|
||||||
|
|
||||||
let titleView = CounterContollerTitleView(theme: self.presentationData.theme)
|
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.navigationItem.titleView = titleView
|
||||||
|
self.titleView = titleView
|
||||||
|
|
||||||
self.moreButtonNode.action = { [weak self] _, gesture in
|
self.moreButtonNode.action = { [weak self] _, gesture in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture)
|
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) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
@ -215,6 +348,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
assert(true)
|
assert(true)
|
||||||
|
self.presentationDataDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func cancelPressed() {
|
@objc private func cancelPressed() {
|
||||||
@ -226,42 +360,61 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||||
var items: [ContextMenuItem] = []
|
let context = self.context
|
||||||
items.append(.action(ContextMenuActionItem(text: "Open Bot", icon: { theme in
|
let presentationData = self.presentationData
|
||||||
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 {
|
let peerId = self.peerId
|
||||||
return
|
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 controller = addWebAppToAttachmentController(sharedContext: strongSelf.context.sharedContext)
|
|
||||||
strongSelf.present(controller, in: .window(.root))
|
|
||||||
})))
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Reload Page", icon: { theme in
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
|
|
||||||
|
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Remove Bot", textColor: .destructive, icon: { theme in
|
if let _ = attachMenuBots.firstIndex(where: { $0.peer.id == botId}) {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in
|
||||||
}, action: { _, f in
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||||
f(.default)
|
}, action: { [weak self] _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
})))
|
if let strongSelf = self {
|
||||||
|
let _ = context.engine.messages.removeBotFromAttachMenu(peerId: strongSelf.botId).start()
|
||||||
|
strongSelf.dismiss()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
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)
|
self.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
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)
|
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) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user