diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4bfa17f7bb..2f82bc7492 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9032,3 +9032,6 @@ Sorry for the inconvenience."; "Settings.PowerSaving" = "Power Saving"; "Settings.PowerSavingOn" = "On"; "Settings.PowerSavingOff" = "Off"; + +"Conversation.BotApp" = "Bot Application"; +"Conversation.OpenBotApp" = "OPEN APP"; diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index 3efb9d152a..d00ec3d92f 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -41,7 +41,7 @@ public final class PeerSelectionControllerParams { public let context: AccountContext public let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let filter: ChatListNodePeersFilter - public let requestPeerType: ReplyMarkupButtonRequestPeerType? + public let requestPeerType: [ReplyMarkupButtonRequestPeerType]? public let forumPeerId: EnginePeer.Id? public let hasFilters: Bool public let hasChatListSelector: Bool @@ -55,8 +55,9 @@ public final class PeerSelectionControllerParams { public let forwardedMessageIds: [EngineMessage.Id] public let hasTypeHeaders: Bool public let selectForumThreads: Bool + public let hasCreation: Bool - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filter: ChatListNodePeersFilter = [.onlyWriteable], requestPeerType: ReplyMarkupButtonRequestPeerType? = nil, forumPeerId: EnginePeer.Id? = nil, hasFilters: Bool = false, hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer, Int64?) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filter: ChatListNodePeersFilter = [.onlyWriteable], requestPeerType: [ReplyMarkupButtonRequestPeerType]? = nil, forumPeerId: EnginePeer.Id? = nil, hasFilters: Bool = false, hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer, Int64?) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessageIds: [EngineMessage.Id] = [], hasTypeHeaders: Bool = false, selectForumThreads: Bool = false, hasCreation: Bool = false) { self.context = context self.updatedPresentationData = updatedPresentationData self.filter = filter @@ -74,6 +75,7 @@ public final class PeerSelectionControllerParams { self.forwardedMessageIds = forwardedMessageIds self.hasTypeHeaders = hasTypeHeaders self.selectForumThreads = selectForumThreads + self.hasCreation = hasCreation } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 7fe7df2d50..0cd645ae5d 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -86,7 +86,7 @@ private struct ChatListSearchContainerNodeSearchState: Equatable { public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext private let peersFilter: ChatListNodePeersFilter - private let requestPeerType: ReplyMarkupButtonRequestPeerType? + private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private let location: ChatListControllerLocation private let displaySearchFilters: Bool private let hasDownloads: Bool @@ -140,7 +140,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private var validLayout: (ContainerViewLayout, CGFloat)? - public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) { + public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) { var initialFilter = initialFilter if case .chats = initialFilter, case .forum = location { initialFilter = .topics diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index e28137e442..a08f64a149 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -461,7 +461,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } - public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { + public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem { switch self { case let .topic(peer, threadInfo, _, theme, strings, expandType): let actionTitle: String? @@ -830,7 +830,7 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -937,7 +937,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private let animationRenderer: MultiAnimationRenderer private let interaction: ChatListSearchInteraction private let peersFilter: ChatListNodePeersFilter - private let requestPeerType: ReplyMarkupButtonRequestPeerType? + private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private var presentationData: PresentationData private let key: ChatListSearchPaneKey private let tagMask: EngineMessage.Tags? @@ -1007,7 +1007,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private var hiddenMediaDisposable: Disposable? - init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { + init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer @@ -1651,116 +1651,127 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1)) let filteredPeer: (EnginePeer, EnginePeer) -> Bool = { peer, accountPeer in - if let peerType = requestPeerType { + if let requestPeerType { guard !peer.isDeleted && peer.id != context.account.peerId else { return false } - switch peerType { - case let .user(userType): - if case let .user(user) = peer { - if let isBot = userType.isBot { - if isBot != (user.botInfo != nil) { - return false - } - } - if let isPremium = userType.isPremium { - if isPremium != user.isPremium { - return false - } - } - return true - } else { - return false + + var match = false + for peerType in requestPeerType { + if match { + break } - case let .group(groupType): - if case let .legacyGroup(group) = peer { - if groupType.isCreator { - if case .creator = group.role { - } else { - return false - } - } - if let isForum = groupType.isForum, isForum { - return false - } - if let hasUsername = groupType.hasUsername, hasUsername { - return false - } - if let userAdminRights = groupType.userAdminRights { - if case .creator = group.role, userAdminRights.rights.contains(.canBeAnonymous) { - return false - } else if case let .admin(rights, _) = group.role { - if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { - return false + switch peerType { + case let .user(userType): + if case let .user(user) = peer { + match = true + if let isBot = userType.isBot { + if isBot != (user.botInfo != nil) { + match = false } - } else if case .member = group.role { - return false } - } - return true - } else if case let .channel(channel) = peer, case .group = channel.info { - if groupType.isCreator { - if !channel.flags.contains(.isCreator) { - return false - } - } - if let isForum = groupType.isForum { - if isForum != channel.flags.contains(.isForum) { - return false - } - } - if let hasUsername = groupType.hasUsername { - if hasUsername != (!(channel.addressName ?? "").isEmpty) { - return false - } - } - if let userAdminRights = groupType.userAdminRights { - if channel.flags.contains(.isCreator) { - if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { - return false + if let isPremium = userType.isPremium { + if isPremium != user.isPremium { + match = false } - } else if let rights = channel.adminRights { - if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { - return false - } - } else { - return false } + } else { + match = false + } + case let .group(groupType): + if case let .legacyGroup(group) = peer { + match = true + if groupType.isCreator { + if case .creator = group.role { + } else { + match = false + } + } + if let isForum = groupType.isForum, isForum { + match = false + } + if let hasUsername = groupType.hasUsername, hasUsername { + match = false + } + if let userAdminRights = groupType.userAdminRights { + if case .creator = group.role, userAdminRights.rights.contains(.canBeAnonymous) { + match = false + } else if case let .admin(rights, _) = group.role { + if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { + match = false + } + } else if case .member = group.role { + match = false + } + } + } else if case let .channel(channel) = peer, case .group = channel.info { + match = true + if groupType.isCreator { + if !channel.flags.contains(.isCreator) { + match = false + } + } + if let isForum = groupType.isForum { + if isForum != channel.flags.contains(.isForum) { + match = false + } + } + if let hasUsername = groupType.hasUsername { + if hasUsername != (!(channel.addressName ?? "").isEmpty) { + match = false + } + } + if let userAdminRights = groupType.userAdminRights { + if channel.flags.contains(.isCreator) { + if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { + match = false + } + } else if let rights = channel.adminRights { + if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { + match = false + } + } else { + match = false + } + } + } else { + match = false + } + case let .channel(channelType): + if case let .channel(channel) = peer, case .broadcast = channel.info { + match = true + if channelType.isCreator { + if !channel.flags.contains(.isCreator) { + match = false + } + } + if let hasUsername = channelType.hasUsername { + if hasUsername != (!(channel.addressName ?? "").isEmpty) { + match = false + } + } + if let userAdminRights = channelType.userAdminRights { + if channel.flags.contains(.isCreator) { + if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { + match = false + } + } else if let rights = channel.adminRights { + if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { + match = false + } + } else { + match = false + } + } + } else { + match = false } - return true - } else { - return false } - case let .channel(channelType): - if case let .channel(channel) = peer, case .broadcast = channel.info { - if channelType.isCreator { - if !channel.flags.contains(.isCreator) { - return false - } - } - if let hasUsername = channelType.hasUsername { - if hasUsername != (!(channel.addressName ?? "").isEmpty) { - return false - } - } - if let userAdminRights = channelType.userAdminRights { - if channel.flags.contains(.isCreator) { - if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { - return false - } - } else if let rights = channel.adminRights { - if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { - return false - } - } else { - return false - } - } + if match { return true - } else { - return false } } + return false } else { guard !peersFilter.contains(.excludeSavedMessages) || peer.id != accountPeer.id else { return false } guard !peersFilter.contains(.excludeSecretChats) || peer.id.namespace != Namespaces.Peer.SecretChat else { return false } diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index 30dcddb37a..504608aaf2 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -119,7 +119,7 @@ private final class ChatListSearchPendingPane { interaction: ChatListSearchInteraction, navigationController: NavigationController?, peersFilter: ChatListNodePeersFilter, - requestPeerType: ReplyMarkupButtonRequestPeerType?, + requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal, searchOptions: Signal, @@ -148,7 +148,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD private let animationRenderer: MultiAnimationRenderer private let updatedPresentationData: (initial: PresentationData, signal: Signal)? private let peersFilter: ChatListNodePeersFilter - private let requestPeerType: ReplyMarkupButtonRequestPeerType? + private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private let location: ChatListControllerLocation private let searchQuery: Signal private let searchOptions: Signal @@ -183,7 +183,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD private var currentAvailablePanes: [ChatListSearchPaneKey]? - init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peersFilter: ChatListNodePeersFilter, requestPeerType: ReplyMarkupButtonRequestPeerType?, location: ChatListControllerLocation, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { + init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index d8f8edc90d..023a8a2991 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1835,7 +1835,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch entity.type { case .Spoiler, .CustomEmoji: return true - case .Strikethrough, .Italic, .Bold: + case .Strikethrough, .Underline, .Italic, .Bold: return true default: return false diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 0ecd93e6df..b02fd24258 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -21,7 +21,7 @@ import Postbox public enum ChatListNodeMode { case chatList case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool) - case peerType(type: ReplyMarkupButtonRequestPeerType) + case peerType(type: [ReplyMarkupButtonRequestPeerType], hasCreate: Bool) } struct ChatListNodeListViewTransition { @@ -1749,118 +1749,128 @@ public final class ChatListNode: ListView { isEmpty = false return true - case let .peerType(peerType): + case let .peerType(peerTypes, _): if let peer = peer.peer, !peer.isDeleted && peer.id != context.account.peerId { - switch peerType { - case let .user(userType): - if case let .user(user) = peer { - if let isBot = userType.isBot { - if isBot != (user.botInfo != nil) { - return false - } - } - if let isPremium = userType.isPremium { - if isPremium != user.isPremium { - return false - } - } - isEmpty = false - return true - } else { - return false + var match = false + for peerType in peerTypes { + if match { + break } - case let .group(groupType): - if case let .legacyGroup(group) = peer { - if groupType.isCreator { - if case .creator = group.role { - } else { - return false - } - } - if let isForum = groupType.isForum, isForum { - return false - } - if let hasUsername = groupType.hasUsername, hasUsername { - return false - } - if let userAdminRights = groupType.userAdminRights { - if case .creator = group.role, userAdminRights.rights.contains(.canBeAnonymous) { - return false - } else if case let .admin(rights, _) = group.role { - if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { - return false + switch peerType { + case let .user(userType): + if case let .user(user) = peer { + match = true + if let isBot = userType.isBot { + if isBot != (user.botInfo != nil) { + match = false } - } else if case .member = group.role { - return false } - } - isEmpty = false - return true - } else if case let .channel(channel) = peer, case .group = channel.info { - if groupType.isCreator { - if !channel.flags.contains(.isCreator) { - return false - } - } - if let isForum = groupType.isForum { - if isForum != channel.flags.contains(.isForum) { - return false - } - } - if let hasUsername = groupType.hasUsername { - if hasUsername != (!(channel.addressName ?? "").isEmpty) { - return false - } - } - if let userAdminRights = groupType.userAdminRights { - if channel.flags.contains(.isCreator) { - if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { - return false + if let isPremium = userType.isPremium { + if isPremium != user.isPremium { + match = false } - } else if let rights = channel.adminRights { - if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { - return false - } - } else { - return false } + isEmpty = false + } else { + match = false + } + case let .group(groupType): + if case let .legacyGroup(group) = peer { + match = true + if groupType.isCreator { + if case .creator = group.role { + } else { + match = false + } + } + if let isForum = groupType.isForum, isForum { + match = false + } + if let hasUsername = groupType.hasUsername, hasUsername { + match = false + } + if let userAdminRights = groupType.userAdminRights { + if case .creator = group.role, userAdminRights.rights.contains(.canBeAnonymous) { + match = false + } else if case let .admin(rights, _) = group.role { + if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { + match = false + } + } else if case .member = group.role { + match = false + } + } + isEmpty = false + } else if case let .channel(channel) = peer, case .group = channel.info { + match = true + if groupType.isCreator { + if !channel.flags.contains(.isCreator) { + match = false + } + } + if let isForum = groupType.isForum { + if isForum != channel.flags.contains(.isForum) { + match = false + } + } + if let hasUsername = groupType.hasUsername { + if hasUsername != (!(channel.addressName ?? "").isEmpty) { + match = false + } + } + if let userAdminRights = groupType.userAdminRights { + if channel.flags.contains(.isCreator) { + if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { + match = false + } + } else if let rights = channel.adminRights { + if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { + match = false + } + } else { + match = false + } + } + isEmpty = false + } else { + match = false + } + case let .channel(channelType): + if case let .channel(channel) = peer, case .broadcast = channel.info { + match = true + if channelType.isCreator { + if !channel.flags.contains(.isCreator) { + match = false + } + } + if let hasUsername = channelType.hasUsername { + if hasUsername != (!(channel.addressName ?? "").isEmpty) { + match = false + } + } + if let userAdminRights = channelType.userAdminRights { + if channel.flags.contains(.isCreator) { + if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { + match = false + } + } else if let rights = channel.adminRights { + if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { + match = false + } + } else { + match = false + } + } + isEmpty = false + } else { + match = false } - isEmpty = false - return true - } else { - return false } - case let .channel(channelType): - if case let .channel(channel) = peer, case .broadcast = channel.info { - if channelType.isCreator { - if !channel.flags.contains(.isCreator) { - return false - } - } - if let hasUsername = channelType.hasUsername { - if hasUsername != (!(channel.addressName ?? "").isEmpty) { - return false - } - } - if let userAdminRights = channelType.userAdminRights { - if channel.flags.contains(.isCreator) { - if let rights = channel.adminRights, rights.rights.contains(.canBeAnonymous) != userAdminRights.rights.contains(.canBeAnonymous) { - return false - } - } else if let rights = channel.adminRights { - if rights.rights.intersection(userAdminRights.rights) != userAdminRights.rights { - return false - } - } else { - return false - } - } - isEmpty = false + if match { return true - } else { - return false } } + return false } else { return false } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 55cbb5e6a0..7cfd4f5ad9 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -683,14 +683,16 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData)) index += 1 } - } else if case let .peerType(type) = mode, !result.isEmpty { - switch type { - case .group: - result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewGroup, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData)) - case .channel: - result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewChannel, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData)) - default: - break + } else if case let .peerType(types, hasCreate) = mode, !result.isEmpty && hasCreate { + for type in types { + switch type { + case .group: + result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewGroup, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData)) + case .channel: + result.append(.AdditionalCategory(index: 0, id: 0, title: state.presentationData.strings.RequestPeer_CreateNewChannel, image: PresentationResourcesItemList.createGroupIcon(state.presentationData.theme), appearance: .action, selected: false, presentationData: state.presentationData)) + default: + break + } } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 87bcec1876..9b592a150d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3524,17 +3524,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let _ = strongSelf.presentationInterfaceState.recordedMediaPreview { strongSelf.sendMediaRecording(scheduleTime: time) } else { - strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: time, completion: { [weak self] in + strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: time) { [weak self] in if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: strongSelf.presentationInterfaceState.subject != .scheduledMessages, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } }) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } - }) + } } } }) @@ -4148,12 +4148,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let params = WebAppParameters(peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: false, isInline: isInline, isSimple: true) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) - }, requestSwitchInline: { [weak self] query, chatTypes in + }, requestSwitchInline: { [weak self] query, chatTypes, completion in if let strongSelf = self { - if let _ = chatTypes { - let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: nil, hasContactSelector: false)) + if let chatTypes { + let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false)) controller.peerSelected = { [weak self, weak controller] peer, _ in if let strongSelf = self { + completion() controller?.dismiss() strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)") } @@ -4255,9 +4256,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let context = self.context let peerId = self.chatLocation.peerId var createNewGroupImpl: (() -> Void)? - let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: peerType, hasContactSelector: false, createNewGroup: { + let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: [peerType], hasContactSelector: false, createNewGroup: { createNewGroupImpl?() - })) + }, hasCreation: true)) let presentConfirmation: (String, Bool, @escaping () -> Void) -> Void = { [weak self] peerName, isChannel, completion in guard let strongSelf = self else { @@ -17981,6 +17982,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case .present = presence.status { sendWhenOnlineAvailable = true } + if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { + sendWhenOnlineAvailable = false + } let mode: ChatScheduleTimeControllerMode if peerId == strongSelf.context.account.peerId { diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 1caa6a964a..2a6388245d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -182,7 +182,9 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } let entities = messageEntities.filter { entity in - if case .Spoiler = entity.type { + if case .Strikethrough = entity.type { + return true + } else if case .Spoiler = entity.type { return true } else if case .CustomEmoji = entity.type { return true diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 201c0d9968..efb403b296 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -318,6 +318,9 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { title = item.presentationData.strings.Conversation_Theme text = nil actionTitle = item.presentationData.strings.Conversation_ViewTheme + case "telegram_botapp": + title = item.presentationData.strings.Conversation_BotApp + actionTitle = item.presentationData.strings.Conversation_OpenBotApp default: break } diff --git a/submodules/TelegramUI/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Sources/PeerSelectionController.swift index 2d10245531..45fca20c4d 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionController.swift @@ -63,7 +63,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private let pretendPresentedInModal: Bool private let forwardedMessageIds: [EngineMessage.Id] private let hasTypeHeaders: Bool - private let requestPeerType: ReplyMarkupButtonRequestPeerType? + private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? + private let hasCreation: Bool override public var _presentedInModal: Bool { get { @@ -103,6 +104,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.hasTypeHeaders = params.hasTypeHeaders self.selectForumThreads = params.selectForumThreads self.requestPeerType = params.requestPeerType + self.hasCreation = params.hasCreation super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -110,18 +112,22 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.customTitle = params.title - if let peerType = params.requestPeerType { - switch peerType { - case let .user(user): - if let isBot = user.isBot, isBot { - self.customTitle = self.presentationData.strings.RequestPeer_ChooseBotTitle - } else { - self.customTitle = self.presentationData.strings.RequestPeer_ChooseUserTitle + if let peerTypes = params.requestPeerType { + if peerTypes.count == 1, let peerType = peerTypes.first { + switch peerType { + case let .user(user): + if let isBot = user.isBot, isBot { + self.customTitle = self.presentationData.strings.RequestPeer_ChooseBotTitle + } else { + self.customTitle = self.presentationData.strings.RequestPeer_ChooseUserTitle + } + case .group: + self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle + case .channel: + self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle } - case .group: - self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle - case .channel: - self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle + } else { + self.customTitle = self.presentationData.strings.ChatImport_Title } } @@ -143,7 +149,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } self.presentationDataDisposable = ((params.updatedPresentationData?.signal ?? self.context.sharedContext.presentationData) - |> deliverOnMainQueue).start(next: { [weak self] presentationData in + |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings @@ -216,7 +222,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } override public func loadDisplayNode() { - self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasFilters: self.hasFilters, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, createNewGroup: self.createNewGroup, present: { [weak self] c, a in + self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasFilters: self.hasFilters, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, hasCreation: self.hasCreation, createNewGroup: self.createNewGroup, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, presentInGlobalOverlay: { [weak self] c, a in self?.presentInGlobalOverlay(c, with: a) diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index bd1f1dfe9c..6c1eb7c078 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -33,7 +33,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let hasGlobalSearch: Bool private let forwardedMessageIds: [EngineMessage.Id] private let hasTypeHeaders: Bool - private let requestPeerType: ReplyMarkupButtonRequestPeerType? + private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private var presentationInterfaceState: ChatPresentationInterfaceState private var interfaceInteraction: ChatPanelInterfaceInteraction? @@ -105,7 +105,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return (self.presentationData, self.presentationDataPromise.get()) } - init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: EnginePeer.Id?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: ReplyMarkupButtonRequestPeerType?, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { + init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: EnginePeer.Id?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, hasCreation: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { self.context = context self.controller = controller self.present = present @@ -196,7 +196,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { let chatListMode: ChatListNodeMode if let requestPeerType = self.requestPeerType { - chatListMode = .peerType(type: requestPeerType) + chatListMode = .peerType(type: requestPeerType, hasCreate: hasCreation) } else { chatListMode = .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil, displayAutoremoveTimeout: false) } @@ -731,7 +731,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { mainContainerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: actualNavigationBarHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: navigationBarHeight, insets: insets, isReorderingFilters: false, isEditing: false, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0, transition: transition) } - if let requestPeerType = self.requestPeerType { + if let requestPeerTypes = self.requestPeerType, let requestPeerType = requestPeerTypes.first { if self.isEmpty { self.chatListNode?.isHidden = true self.requirementsBackgroundNode?.isHidden = true diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index b5611154d7..34dd2b17ea 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -149,6 +149,8 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { switch entity.type { case .Spoiler, .CustomEmoji: return true + case .Strikethrough: + return true default: return false } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 13b7700ea3..bdeba308ab 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -627,11 +627,28 @@ public final class WebAppController: ViewController, AttachmentContainable { self.animateTransitionIn() case "web_app_switch_inline_query": if controller.isInline, let json, let query = json["query"] as? String { - controller.dismiss() if let chatTypes = json["chat_types"] as? [String], !chatTypes.isEmpty { - controller.requestSwitchInline(query, .user(.init(isBot: nil, isPremium: nil))) + var requestPeerTypes: [ReplyMarkupButtonRequestPeerType] = [] + for type in chatTypes { + switch type { + case "users": + requestPeerTypes.append(.user(ReplyMarkupButtonRequestPeerType.User(isBot: false, isPremium: nil))) + case "bots": + requestPeerTypes.append(.user(ReplyMarkupButtonRequestPeerType.User(isBot: true, isPremium: nil))) + case "groups": + requestPeerTypes.append(.group(ReplyMarkupButtonRequestPeerType.Group(isCreator: false, hasUsername: nil, isForum: nil, botParticipant: false, userAdminRights: nil, botAdminRights: nil))) + case "channels": + requestPeerTypes.append(.channel(ReplyMarkupButtonRequestPeerType.Channel(isCreator: false, hasUsername: nil, userAdminRights: nil, botAdminRights: nil))) + default: + break + } + } + controller.requestSwitchInline(query, requestPeerTypes, { [weak controller] in + controller?.dismiss() + }) } else { - controller.requestSwitchInline(query, nil) + controller.dismiss() + controller.requestSwitchInline(query, nil, {}) } } case "web_app_data_send": @@ -1054,7 +1071,7 @@ public final class WebAppController: ViewController, AttachmentContainable { public var openUrl: (String) -> Void = { _ in } public var getNavigationController: () -> NavigationController? = { return nil } public var completion: () -> Void = {} - public var requestSwitchInline: (String, ReplyMarkupButtonRequestPeerType?) -> Void = { _, _ in } + public var requestSwitchInline: (String, [ReplyMarkupButtonRequestPeerType]?, @escaping () -> Void) -> Void = { _, _, _ in } public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) { self.context = context @@ -1349,7 +1366,7 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent } } -public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, threadId: Int64?, openUrl: @escaping (String) -> Void, requestSwitchInline: @escaping (String, ReplyMarkupButtonRequestPeerType?) -> Void = { _, _ in }, getInputContainerNode: @escaping () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil }, completion: @escaping () -> Void = {}, willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, getSourceRect: (() -> CGRect?)? = nil) -> ViewController { +public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, threadId: Int64?, openUrl: @escaping (String) -> Void, requestSwitchInline: @escaping (String, [ReplyMarkupButtonRequestPeerType]?, @escaping () -> Void) -> Void = { _, _, _ in }, getInputContainerNode: @escaping () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil }, completion: @escaping () -> Void = {}, willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, getSourceRect: (() -> CGRect?)? = nil) -> ViewController { let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu, hasTextInput: false, makeEntityInputView: { return nil })