diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b0cf47ecc5..bdd4e899fb 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7418,6 +7418,9 @@ Sorry for the inconvenience."; "WebApp.AddToAttachmentAlreadyAddedError" = "This bot is already added in the attachment menu."; +"WebApp.OpenWebViewAlertTitle" = "Open Web App"; +"WebApp.OpenWebViewAlertText" = "**%@** would like to open its web app to proceed.\n\nIt will be able to access your **IP address** and basic device info."; + "WebApp.MessagePreview" = "Message Preview"; "WebApp.Send" = "Send"; diff --git a/submodules/AlertUI/Sources/ThemedTextAlertController.swift b/submodules/AlertUI/Sources/ThemedTextAlertController.swift index 8cd9de35ac..99c962e8f9 100644 --- a/submodules/AlertUI/Sources/ThemedTextAlertController.swift +++ b/submodules/AlertUI/Sources/ThemedTextAlertController.swift @@ -13,8 +13,8 @@ public final class AlertControllerContext { } } -public func textAlertController(alertContext: AlertControllerContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController { - let controller = standardTextAlertController(theme: alertContext.theme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap) +public func textAlertController(alertContext: AlertControllerContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true) -> AlertController { + let controller = standardTextAlertController(theme: alertContext.theme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap) let presentationDataDisposable = alertContext.themeSignal.start(next: { [weak controller] theme in controller?.theme = theme }) diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index edac0408b5..6d6b52f637 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -144,8 +144,11 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { return false } + var isTracking: Bool { + return self.panGestureArguments != nil + } + private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { guard let (layout, controllers, coveredByModalTransition) = self.validLayout else { return diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index de0ef04311..1cc2a9e8a6 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -18,6 +18,7 @@ public enum AttachmentButtonType: Equatable { case contact case poll case app(PeerId, String, TelegramMediaFile) + case standalone } public protocol AttachmentContainable: ViewController { @@ -25,6 +26,7 @@ public protocol AttachmentContainable: ViewController { var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void { get set } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void { get set } var cancelPanGesture: () -> Void { get set } + var isContainerPanning: () -> Bool { get set } func resetForReuse() func prepareForReuse() @@ -349,6 +351,15 @@ public class AttachmentController: ViewController { strongSelf.container.cancelPanGesture() } } + + controller.isContainerPanning = { [weak self] in + if let strongSelf = self { + return strongSelf.container.isTracking + } else { + return false + } + } + let previousController = strongSelf.currentControllers.last strongSelf.currentControllers = [controller] @@ -557,14 +568,21 @@ public class AttachmentController: ViewController { containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) var containerInsets = containerLayout.intrinsicInsets - containerInsets.bottom = panelHeight + var hasPanel = false + if let controller = self.controller, controller.buttons.count > 1 { + hasPanel = true + containerInsets.bottom = panelHeight + } + let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets) self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { self.wrapperNode.addSubnode(self.container) - self.container.addSubnode(self.panel) + if hasPanel { + self.container.addSubnode(self.panel) + } self.animateIn() } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index c85ea1aa01..e7e26f7870 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -184,6 +184,10 @@ private final class AttachButtonComponent: CombinedComponent { name = appName imageName = "" imageFile = appIcon + case .standalone: + name = "" + imageName = "" + imageFile = nil } let tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index 43eec6e9fb..b48388fb84 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -521,6 +521,7 @@ private class CreatePollControllerImpl: ItemListController, AttachmentContainabl public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } } public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> AttachmentContainable { diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index eb8ea5f944..135d855187 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -409,6 +409,7 @@ open class LegacyController: ViewController, PresentableController, AttachmentCo open var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } open var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } open var cancelPanGesture: () -> Void = { } + open var isContainerPanning: () -> Bool = { return false } public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) { self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber)) diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index bb46c8f029..69acc6363f 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -75,6 +75,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: LocationPickerMode, completion: @escaping (TelegramMediaMap, String?) -> Void) { self.context = context diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index eca2079bda..f0a0a43c58 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -133,6 +133,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } var dismissAll: () -> Void = { } diff --git a/submodules/PresentationDataUtils/Sources/AlertTheme.swift b/submodules/PresentationDataUtils/Sources/AlertTheme.swift index bba15a695f..8de1164e1c 100644 --- a/submodules/PresentationDataUtils/Sources/AlertTheme.swift +++ b/submodules/PresentationDataUtils/Sources/AlertTheme.swift @@ -5,11 +5,11 @@ import AccountContext import SwiftSignalKit import TelegramPresentationData -public func textAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController { - return textAlertController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, forceTheme: forceTheme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap) +public func textAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true) -> AlertController { + return textAlertController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, forceTheme: forceTheme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap) } -public func textAlertController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController { +public func textAlertController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true) -> AlertController { var presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } if let forceTheme = forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) @@ -21,7 +21,7 @@ public func textAlertController(sharedContext: SharedAccountContext, updatedPres presentationData = presentationData.withUpdated(theme: forceTheme) } return AlertControllerTheme(presentationData: presentationData) - }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap) + }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap) } public func textAlertController(sharedContext: SharedAccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController { diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift index d0680b9f97..b5faf1458b 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift @@ -35,7 +35,7 @@ public func flatMap(_ f: @escaping (T) -> R) -> (Signal) -> Sign return Signal { subscriber in return signal.start(next: { next in if let next = next { - subscriber.putNext(f(next)) + subscriber.putNext(f(next)) } else { subscriber.putNext(nil) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 42e7c0626a..38b93f1910 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -804,7 +804,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-440534818] = { return Api.Update.parse_updateUserStatus($0) } dict[-1071741569] = { return Api.Update.parse_updateUserTyping($0) } dict[2139689491] = { return Api.Update.parse_updateWebPage($0) } - dict[-118080598] = { return Api.Update.parse_updateWebViewResultSent($0) } + dict[361936797] = { return Api.Update.parse_updateWebViewResultSent($0) } dict[2027216577] = { return Api.Updates.parse_updateShort($0) } dict[1299050149] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[826001400] = { return Api.Updates.parse_updateShortMessage($0) } diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 28a01c2a8c..10fe20346d 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -605,7 +605,7 @@ public extension Api { case updateUserStatus(userId: Int64, status: Api.UserStatus) case updateUserTyping(userId: Int64, action: Api.SendMessageAction) case updateWebPage(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) - case updateWebViewResultSent(peer: Api.Peer, botId: Int64, queryId: Int64) + case updateWebViewResultSent(queryId: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1473,12 +1473,10 @@ public extension Api { serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) break - case .updateWebViewResultSent(let peer, let botId, let queryId): + case .updateWebViewResultSent(let queryId): if boxed { - buffer.appendInt32(-118080598) + buffer.appendInt32(361936797) } - peer.serialize(buffer, true) - serializeInt64(botId, buffer: buffer, boxed: false) serializeInt64(queryId, buffer: buffer, boxed: false) break } @@ -1684,8 +1682,8 @@ public extension Api { return ("updateUserTyping", [("userId", String(describing: userId)), ("action", String(describing: action))]) case .updateWebPage(let webpage, let pts, let ptsCount): return ("updateWebPage", [("webpage", String(describing: webpage)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))]) - case .updateWebViewResultSent(let peer, let botId, let queryId): - return ("updateWebViewResultSent", [("peer", String(describing: peer)), ("botId", String(describing: botId)), ("queryId", String(describing: queryId))]) + case .updateWebViewResultSent(let queryId): + return ("updateWebViewResultSent", [("queryId", String(describing: queryId))]) } } @@ -3449,19 +3447,11 @@ public extension Api { } } public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateWebViewResultSent(peer: _1!, botId: _2!, queryId: _3!) + if _c1 { + return Api.Update.updateWebViewResultSent(queryId: _1!) } else { return nil diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 76c1adf1d7..92f013be9f 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1514,7 +1514,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions, eventTimestamp: updatesDate) case .updateAttachMenuBots: updatedState.addUpdateAttachMenuBots() - case let .updateWebViewResultSent(_, _, queryId): + case let .updateWebViewResultSent(queryId): updatedState.addDismissWebView(queryId) default: break diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index f6025eb842..78ab97ebb1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -188,20 +188,20 @@ private func setCachedAttachMenuBots(transaction: Transaction, attachMenuBots: A } } -func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network) -> Signal { - let poll = Signal { subscriber in - let signal: Signal = cachedAttachMenuBots(postbox: postbox) +func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + let signal: Signal = cachedAttachMenuBots(postbox: postbox) |> mapToSignal { current in return (network.request(Api.functions.messages.getAttachMenuBots(hash: current?.hash ?? 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in guard let result = result else { return .complete() } - return postbox.transaction { transaction in + return postbox.transaction { transaction -> Void in switch result { case let .attachMenuBots(hash, bots, users): var peers: [Peer] = [] @@ -237,11 +237,14 @@ func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network) -> Sig case .attachMenuBotsNotModified: break } - } |> ignoreValues + return Void() + } }) } - return signal.start(completed: { + return signal.start(next: { value in + subscriber.putNext(value) + }, completed: { subscriber.putCompletion() }) } @@ -273,9 +276,16 @@ func _internal_addBotToAttachMenu(postbox: Postbox, network: Network, botId: Pee |> `catch` { error -> Signal in return .single(false) } - |> afterCompleted { - let _ = (managedSynchronizeAttachMenuBots(postbox: postbox, network: network) - |> take(1)).start() + |> mapToSignal { value -> Signal in + if value { + return managedSynchronizeAttachMenuBots(postbox: postbox, network: network) + |> take(1) + |> map { _ -> Bool in + return true + } + } else { + return .single(value) + } } } |> switchToLatest diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index 494dfcbe5c..d428f8f4f0 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -168,6 +168,7 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } var delayDisappear = false diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 1489495bc2..8f1e0f9ce1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3302,7 +3302,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil) }, openWebView: { [weak self] buttonText, url, simple in - guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser else { return } @@ -3350,48 +3350,76 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - if simple { - strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestSimpleWebView(botId: peerId, url: url, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme)) - |> afterDisposed { - updateProgress() - }) - |> deliverOnMainQueue).start(next: { [weak self] url in - guard let strongSelf = self else { - return - } - let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil, replyToMessageId: nil, iconFile: nil) - controller.getNavigationController = { [weak self] in - return self?.effectiveNavigationController - } - controller.navigationPresentation = .modal - strongSelf.push(controller) - }, 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)) - } - })) + let openWebView = { + if simple { + strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestSimpleWebView(botId: peerId, url: url, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme)) + |> afterDisposed { + updateProgress() + }) + |> deliverOnMainQueue).start(next: { [weak self] url in + guard let strongSelf = self else { + return + } + + let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, buttonText: buttonText, keepAliveSignal: nil) + strongSelf.present(controller, in: .window(.root)) + // controller.getNavigationController = { [weak self] in + // return self?.effectiveNavigationController + // } + // controller.navigationPresentation = .modal + // strongSelf.push(controller) + }, 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)) + } + })) + } else { + strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: url, payload: nil, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), replyToMessageId: nil) + |> afterDisposed { + updateProgress() + }) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let strongSelf = self else { + return + } + let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal) + strongSelf.present(controller, in: .window(.root)) + // let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, replyToMessageId: nil, iconFile: nil) + // controller.getNavigationController = { [weak self] in + // return self?.effectiveNavigationController + // } + // controller.navigationPresentation = .modal + // strongSelf.push(controller) + }, 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)) + } + })) + } + } + + if peer.flags.contains(.isVerified) { + openWebView() } else { - strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: url, payload: nil, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), replyToMessageId: nil) - |> afterDisposed { - updateProgress() - }) - |> deliverOnMainQueue).start(next: { [weak self] result in + let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id) + |> deliverOnMainQueue).start(next: { value in guard let strongSelf = self else { return } - let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, replyToMessageId: nil, iconFile: nil) - controller.getNavigationController = { [weak self] in - return self?.effectiveNavigationController + + if value { + openWebView() + } else { + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertTitle, text: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertText(botName).string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { + if let strongSelf = self { + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start() + openWebView() + } + })], parseMarkdown: true), in: .window(.root), with: nil) } - controller.navigationPresentation = .modal - strongSelf.push(controller) - }, error: { [weak self] error in - if let strongSelf = self { - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - })]), in: .window(.root)) - } - })) + }) } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { @@ -10878,6 +10906,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } completion(controller, nil) strongSelf.controllerNavigationDisposable.set(nil) + default: + break } } let present = { diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index f805c0e89d..14d770c89f 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -79,6 +79,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } var cancelPanGesture: () -> Void = { } + var isContainerPanning: () -> Bool = { return false } init(_ params: ContactSelectionControllerParams) { self.context = params.context diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index bcba6d43c3..4f8ec371d6 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -567,9 +567,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur return } let controller = addWebAppToAttachmentController(context: context, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peerIcon: icon, completion: { - let _ = context.engine.messages.addBotToAttachMenu(botId: peerId).start() - - Queue.mainQueue().after(1.0, { + let _ = (context.engine.messages.addBotToAttachMenu(botId: peerId) + |> deliverOnMainQueue).start(completed: { if let navigationController = navigationController, case let .chat(chatPeerId, _) = urlContext { let _ = context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: chatPeerId), attachBotStart: ChatControllerInitialAttachBotStart(botId: peer.id, payload: payload), useExisting: true)) } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index f86957711a..18b3b06f03 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -86,7 +86,7 @@ public enum ParsedInternalUrl { case share(url: String?, text: String?, to: String?) case wallpaper(WallpaperUrlParameter) case theme(String) - case phone(String) + case phone(String, String?, String?) case startAttach(String, String?) } @@ -223,7 +223,21 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } else if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") { let component = pathComponents[0].replacingOccurrences(of: "%20", with: "+") if component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789+").inverted) == nil { - return .phone(component.replacingOccurrences(of: "+", with: "")) + var attach: String? + var startAttach: String? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "attach" { + attach = value + } else if queryItem.name == "startattach" { + startAttach = value + } + } + } + } + + return .phone(component.replacingOccurrences(of: "+", with: ""), attach, startAttach) } else { return .join(String(component.dropFirst())) } @@ -422,14 +436,26 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) -> Signal { switch url { - case let .phone(phone): + case let .phone(phone, attach, startAttach): return context.engine.peers.resolvePeerByPhone(phone: phone) |> take(1) - |> map { peer -> ResolvedUrl? in + |> mapToSignal { peer -> Signal in if let peer = peer?._asPeer() { - return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) + if let attach = attach { + return context.engine.peers.resolvePeerByName(name: attach) + |> take(1) + |> map { botPeer -> ResolvedUrl? in + if let botPeer = botPeer?._asPeer() { + return .peer(peer.id, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach))) + } else { + return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) + } + } + } else { + return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + } } else { - return .peer(nil, .info) + return .single(.peer(nil, .info)) } } case let .peerName(name, parameter): diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 3758199eca..4e6a4b14be 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -113,11 +113,12 @@ public final class WebAppController: ViewController, AttachmentContainable { public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } + public var isContainerPanning: () -> Bool = { return false } private class Node: ViewControllerTracingNode, WKNavigationDelegate, UIScrollViewDelegate { private weak var controller: WebAppController? - fileprivate var webView: WKWebView? + fileprivate var webView: WebAppWebView? private var placeholderIcon: UIImage? private var placeholderNode: ShimmerEffectNode? @@ -183,7 +184,7 @@ public final class WebAppController: ViewController, AttachmentContainable { configuration.mediaPlaybackRequiresUserAction = false } - let webView = WKWebView(frame: CGRect(), configuration: configuration) + let webView = WebAppWebView(frame: CGRect(), configuration: configuration) webView.alpha = 0.0 webView.isOpaque = false webView.backgroundColor = .clear @@ -200,6 +201,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } webView.allowsBackForwardNavigationGestures = false webView.scrollView.delegate = self + webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0) webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: [], context: nil) webView.tintColor = self.presentationData.theme.rootController.tabBar.iconColor self.webView = webView @@ -334,40 +336,15 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) } - private var animationProgress: CGFloat = 0.0 + private var validLayout: (ContainerViewLayout, CGFloat)? func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - if let webView = self.webView { - let 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 - layout.additionalInsets.bottom))) - if case let .animated(duration, curve) = transition, let springAnimation = transition.animation(), webView.frame != frame { - let initial = webView.frame + let previous = self.validLayout?.0 + self.validLayout = (layout, navigationBarHeight) - let animation = POPBasicAnimation() - animation.property = (POPAnimatableProperty.property(withName: "frame", initializer: { property in - property?.readBlock = { node, values in - values?.pointee = (node as! Node).animationProgress - } - property?.writeBlock = { node, values in - (node as! Node).animationProgress = values!.pointee - var mappedValue = values!.pointee - switch curve { - case .spring: - mappedValue = springAnimationValueAt(springAnimation, mappedValue) - default: - break - } - let currentFrame = CGRect.interpolator()(initial, frame, mappedValue) as! CGRect - (node as! Node).webView?.frame = currentFrame - } - property?.threshold = 0.01 - }) as! POPAnimatableProperty) - animation.fromValue = 0.0 as NSNumber - animation.toValue = 1.0 as NSNumber - animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - animation.duration = duration * Double(springAnimation.speed) - self.pop_add(animation, forKey: "frame") - } else { - webView.frame = frame - } + if let webView = self.webView, let controller = self.controller { + let 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 - layout.additionalInsets.bottom))) + + webView.updateFrame(frame: frame, panning: controller.isContainerPanning(), transition: transition) } if let placeholderNode = self.placeholderNode { @@ -387,6 +364,10 @@ public final class WebAppController: ViewController, AttachmentContainable { let loadingProgressHeight: CGFloat = 2.0 transition.updateFrame(node: self.loadingProgressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height - loadingProgressHeight), size: CGSize(width: layout.size.width, height: loadingProgressHeight))) } + + if let previous = previous, (previous.inputHeight ?? 0.0).isZero, let inputHeight = layout.inputHeight, inputHeight > 44.0 { + self.controller?.requestAttachmentMenuExpansion() + } } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { @@ -671,3 +652,11 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) } } + +public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, botId: PeerId, botName: String, url: String, queryId: Int64?, buttonText: String?, keepAliveSignal: Signal?) -> ViewController { + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: peerId), buttons: [.standalone], initialButton: .standalone) + controller.requestController = { _, completion in + completion(WebAppController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, botId: botId, botName: botName, url: url, queryId: queryId, buttonText: buttonText, keepAliveSignal: keepAliveSignal, replyToMessageId: nil, iconFile: nil), nil) + } + return controller +} diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift new file mode 100644 index 0000000000..0d6950582d --- /dev/null +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -0,0 +1,148 @@ +import Foundation +import UIKit +import Display +import WebKit +import SwiftSignalKit + +private let findFixedPositionClasses = """ +function findFixedPositionClasses() { + var elems = document.body.getElementsByTagName("*"); + var len = elems.length + + var result = [] + var j = 0; + for (var i = 0; i < len; i++) { + if ((window.getComputedStyle(elems[i],null).getPropertyValue('position') == 'fixed') && (window.getComputedStyle(elems[i],null).getPropertyValue('bottom') == '0px')) { + result[j] = elems[i].className; + j++; + } + } + return result; +} +findFixedPositionClasses(); +""" + +private func findFixedPositionViews(webView: WKWebView, classes: [String]) -> [(String, UIView)] { + if let contentView = webView.scrollView.subviews.first { + func recursiveSearch(_ view: UIView) -> [(String, UIView)] { + var result: [(String, UIView)] = [] + + let description = view.description + if description.contains("class='") { + for className in classes { + if description.contains(className) { + result.append((className, view)) + break + } + } + } + + for subview in view.subviews { + result.append(contentsOf: recursiveSearch(subview)) + } + + return result + } + + return recursiveSearch(contentView) + } else { + return [] + } +} + +final class WebAppWebView: WKWebView { + private var fixedPositionClasses: [String] = [] + private var currentFixedViews: [(String, UIView, UIView)] = [] + + private var timer: SwiftSignalKit.Timer? + + deinit { + self.timer?.invalidate() + } + + override func didMoveToSuperview() { + super.didMoveToSuperview() + + if self.timer == nil { + let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.evaluateJavaScript(findFixedPositionClasses, completionHandler: { [weak self] result, _ in + if let result = result { + Queue.mainQueue().async { + self?.fixedPositionClasses = (result as? [String]) ?? [] + } + } + }) + }, queue: Queue.mainQueue()) + timer.start() + self.timer = timer + } + } + + + + func updateFrame(frame: CGRect, panning: Bool, transition: ContainedViewLayoutTransition) { + let reset = { [weak self] in + guard let strongSelf = self else { + return + } + for (_, view, snapshotView) in strongSelf.currentFixedViews { + view.isHidden = false + snapshotView.removeFromSuperview() + } + strongSelf.currentFixedViews = [] + } + + let update = { [weak self] in + guard let strongSelf = self else { + return + } + for (_, view, snapshotView) in strongSelf.currentFixedViews { + view.isHidden = true + + var snapshotFrame = view.frame + snapshotFrame.origin.y = frame.height - snapshotFrame.height + transition.updateFrame(view: snapshotView, frame: snapshotFrame) + } + } + + if panning { + let fixedPositionViews = findFixedPositionViews(webView: self, classes: self.fixedPositionClasses) + if fixedPositionViews.count != self.currentFixedViews.count { + var existing: [String: (UIView, UIView)] = [:] + for (className, originalView, snapshotView) in self.currentFixedViews { + existing[className] = (originalView, snapshotView) + } + + var updatedFixedViews: [(String, UIView, UIView)] = [] + for (className, view) in fixedPositionViews { + if let (_, existingSnapshotView) = existing[className] { + updatedFixedViews.append((className, view, existingSnapshotView)) + existing[className] = nil + } else if let snapshotView = view.snapshotView(afterScreenUpdates: false) { + updatedFixedViews.append((className, view, snapshotView)) + self.addSubview(snapshotView) + } + } + + for (_, originalAndSnapshotView) in existing { + originalAndSnapshotView.0.isHidden = false + originalAndSnapshotView.1.removeFromSuperview() + } + + self.currentFixedViews = updatedFixedViews + } + transition.updateFrame(view: self, frame: frame) + update() + } else { + update() + transition.updateFrame(view: self, frame: frame, completion: { _ in + reset() + }) + } + + } +}