diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index e8a808b9a0..288232c512 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -755,7 +755,7 @@ public class MediaEditorTransitionOutExternalState { } public protocol MediaEditorScreenResult { - + var target: Stories.PendingTarget { get } } public protocol TelegramRootControllerInterface: NavigationController { @@ -963,7 +963,8 @@ public protocol SharedAccountContext: AnyObject { func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) - func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) + func makeInstantPageController(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? + func makeInstantPageController(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 3840144291..75ffce2a03 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -292,12 +292,12 @@ public struct ChatControllerInitialAttachBotStart { } public struct ChatControllerInitialBotAppStart { - public let botApp: BotApp + public let botApp: BotApp? public let payload: String? public let justInstalled: Bool public let compact: Bool - public init(botApp: BotApp, payload: String?, justInstalled: Bool, compact: Bool) { + public init(botApp: BotApp?, payload: String?, justInstalled: Bool, compact: Bool) { self.botApp = botApp self.payload = payload self.justInstalled = justInstalled diff --git a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift index dcb1cd88c8..da83dddb91 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift @@ -281,15 +281,7 @@ final class AddressBarContentComponent: Component { } let isActive = self.textField?.isFirstResponder ?? false - var title: String = "" - if let parsedUrl = URL(string: component.url) { - title = parsedUrl.host ?? component.url - if title.hasPrefix("www.") { - title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4)) - } - title = title.idnaDecoded ?? title - } - + let title = getDisplayUrl(component.url, hostOnly: true) self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition) return availableSize @@ -449,19 +441,7 @@ final class AddressBarContentComponent: Component { textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) } - var address = self.component?.url ?? "" - if let components = URLComponents(string: address) { - if #available(iOS 16.0, *), let encodedHost = components.encodedHost { - if let decodedHost = components.host, encodedHost != decodedHost { - address = address.replacingOccurrences(of: encodedHost, with: decodedHost) - } - } else if let encodedHost = components.host { - if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost { - address = address.replacingOccurrences(of: encodedHost, with: decodedHost) - } - } - } - + let address = getDisplayUrl(self.component?.url ?? "", trim: false) if textField.text != address { textField.text = address self.clearIconView.isHidden = address.isEmpty diff --git a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift index aca503c26d..f41a5395ea 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift @@ -176,23 +176,7 @@ final class BrowserAddressListItemComponent: Component { if case let .Loaded(content) = component.webPage.content { title = content.title ?? content.url - var address = content.url - if let components = URLComponents(string: address) { - if #available(iOS 16.0, *), let encodedHost = components.encodedHost { - if let decodedHost = components.host, encodedHost != decodedHost { - address = address.replacingOccurrences(of: encodedHost, with: decodedHost) - } - } else if let encodedHost = components.host { - if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost { - address = address.replacingOccurrences(of: encodedHost, with: decodedHost) - } - } - } - address = address.replacingOccurrences(of: "https://www.", with: "") - address = address.replacingOccurrences(of: "https://", with: "") - address = address.replacingOccurrences(of: "tonsite://", with: "") - address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/")) - subtitle = address + subtitle = getDisplayUrl(content.url) parsedUrl = URL(string: content.url) diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 64ec20912b..b77e831e36 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -367,7 +367,7 @@ public final class BrowserBookmarksScreen: ViewController { self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) self.title = self.presentationData.strings.WebBrowser_Bookmarks_Title self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in diff --git a/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift b/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift new file mode 100644 index 0000000000..b3f347b7a4 --- /dev/null +++ b/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift @@ -0,0 +1,299 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle +import PhotoResources +import CheckNode +import Markdown + +private let textFont = Font.regular(13.0) +private let boldTextFont = Font.semibold(13.0) + +private func formattedText(_ text: String, color: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { + return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: color), linkAttribute: { _ in return nil}), textAlignment: textAlignment) +} + +private final class BrowserExceptionDomainAlertContentNode: AlertContentNode { + private let strings: PresentationStrings + private let domain: String + + private let titleNode: ASTextNode + private let textNode: ASTextNode + + private let allowWriteCheckNode: InteractiveCheckNode + private let allowWriteLabelNode: ASTextNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private var validLayout: CGSize? + + private var iconDisposable: Disposable? + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + var allowWriteAccess: Bool = true { + didSet { + self.allowWriteCheckNode.setSelected(self.allowWriteAccess, animated: true) + } + } + + init(account: Account, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, domain: String, requestWriteAccess: Bool, actions: [TextAlertAction]) { + self.strings = strings + self.domain = domain + + self.titleNode = ASTextNode() + self.titleNode.maximumNumberOfLines = 0 + + self.textNode = ASTextNode() + self.textNode.maximumNumberOfLines = 0 + + self.allowWriteCheckNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false)) + self.allowWriteCheckNode.setSelected(true, animated: false) + self.allowWriteLabelNode = ASTextNode() + self.allowWriteLabelNode.maximumNumberOfLines = 4 + self.allowWriteLabelNode.isUserInteractionEnabled = true + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + + if requestWriteAccess { + self.addSubnode(self.allowWriteCheckNode) + self.addSubnode(self.allowWriteLabelNode) + } + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.allowWriteCheckNode.valueChanged = { [weak self] value in + if let strongSelf = self { + strongSelf.allowWriteAccess = !strongSelf.allowWriteAccess + } + } + + self.updateTheme(theme) + } + + deinit { + self.iconDisposable?.dispose() + } + + override func didLoad() { + super.didLoad() + + self.allowWriteLabelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.allowWriteTap(_:)))) + } + + @objc private func allowWriteTap(_ gestureRecognizer: UITapGestureRecognizer) { + if self.allowWriteCheckNode.isUserInteractionEnabled { + self.allowWriteAccess = !self.allowWriteAccess + } + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: "Open in Browser", font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: "Do you want to open this link in your default browser?", font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.allowWriteLabelNode.attributedText = formattedText("Always open links from **\(self.domain)** in browser", color: theme.primaryColor) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width , 270.0) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + + let titleSize = self.titleNode.measure(size) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 13.0 + + let textSize = self.textNode.measure(size) + var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize) + origin.y += textSize.height + + var entriesHeight: CGFloat = 0.0 + + if self.allowWriteLabelNode.supernode != nil { + origin.y += 16.0 + entriesHeight += 16.0 + + let checkSize = CGSize(width: 22.0, height: 22.0) + let condensedSize = CGSize(width: size.width - 76.0, height: size.height) + + let allowWriteSize = self.allowWriteLabelNode.measure(condensedSize) + transition.updateFrame(node: self.allowWriteLabelNode, frame: CGRect(origin: CGPoint(x: 46.0, y: origin.y), size: allowWriteSize)) + transition.updateFrame(node: self.allowWriteCheckNode, frame: CGRect(origin: CGPoint(x: 12.0, y: origin.y - 2.0), size: checkSize)) + origin.y += allowWriteSize.height + entriesHeight += allowWriteSize.height + } + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + var contentWidth = max(textSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + entriesHeight + actionsHeight + 17.0 + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0) + transition.updateFrame(node: self.textNode, frame: textFrame) + + return resultSize + } +} + +public func browserExceptionDomainAlertController(context: AccountContext, domain: String, completion: @escaping (Bool) -> Void) -> AlertController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let theme = presentationData.theme + let strings = presentationData.strings + + var dismissImpl: ((Bool) -> Void)? + var getContentNodeImpl: (() -> BrowserExceptionDomainAlertContentNode?)? + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + }), TextAlertAction(type: .defaultAction, title: "Continue", action: { + if let allowWriteAccess = getContentNodeImpl?()?.allowWriteAccess { + completion(allowWriteAccess) + } else { + completion(false) + } + dismissImpl?(true) + })] + + let contentNode = BrowserExceptionDomainAlertContentNode(account: context.account, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, domain: domain, requestWriteAccess: true, actions: actions) + getContentNodeImpl = { [weak contentNode] in + return contentNode + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index c9ce6c8ed8..afd4755c0b 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -384,13 +384,13 @@ private final class BrowserScreenComponent: CombinedComponent { bottomInset: toolbarBottomInset, sideInset: environment.safeInsets.left, item: toolbarContent, - collapseFraction: collapseFraction + collapseFraction: 0.0 ), availableSize: context.availableSize, transition: context.transition ) context.add(toolbar - .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0 + toolbar.size.height * collapseFraction)) .appear(ComponentTransition.Appear { _, view, transition in transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: view.frame.height), to: CGPoint(), additive: true) }) @@ -1390,6 +1390,16 @@ public class BrowserScreen: ViewController, MinimizableController { ] public init(context: AccountContext, subject: Subject, openPreviousOnClose: Bool = false) { + var subject = subject + if case let .webPage(url) = subject, let parsedUrl = URL(string: url) { + if parsedUrl.host?.hasSuffix(".ton") == true { + var urlComponents = URLComponents(string: url) + urlComponents?.scheme = "tonsite" + if let updatedUrl = urlComponents?.url?.absoluteString { + subject = .webPage(url: updatedUrl) + } + } + } self.context = context self.subject = subject self.openPreviousOnClose = openPreviousOnClose diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 0c0f883e0d..2611f92fb9 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -239,12 +239,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU let request = URLRequest(url: parsedUrl) self.webView.load(request) - title = parsedUrl.host ?? "" + title = getDisplayUrl(url, hostOnly: true) } self.errorView = ComponentHostView() - self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, readingProgress: 0.0, contentType: .webPage) + self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.1, readingProgress: 0.0, contentType: .webPage) self.statePromise = Promise(self._state) super.init(frame: .zero) @@ -725,7 +725,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - self.currentError = nil + if let _ = self.currentError { + self.currentError = nil + if let (size, insets, fullInsets, safeInsets) = self.validLayout { + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: .immediate) + } + } self.updateFontState(self.currentFontState, force: true) } @@ -739,7 +744,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - if [-1003, -1100].contains((error as NSError).code) { + if [-1003, -1100, 102].contains((error as NSError).code) { self.currentError = error } else { self.currentError = nil diff --git a/submodules/BrowserUI/Sources/Utils.swift b/submodules/BrowserUI/Sources/Utils.swift index 7caff6dd87..71489f05a3 100644 --- a/submodules/BrowserUI/Sources/Utils.swift +++ b/submodules/BrowserUI/Sources/Utils.swift @@ -108,3 +108,47 @@ func getPrimaryUrl(message: Message) -> String? { } return primaryUrl } + +private let asciiChars = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!) + +func getDisplayUrl(_ url: String, hostOnly: Bool = false, trim: Bool = true) -> String { + if hostOnly { + var title = url + if let parsedUrl = URL(string: url) { + title = parsedUrl.host ?? url + if title.hasPrefix("www.") { + title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4)) + } + if let decoded = title.idnaDecoded, title != decoded { + if decoded.lowercased().rangeOfCharacter(from: asciiChars) == nil { + title = decoded + } + } + } + return title + } else { + var address = url + if let components = URLComponents(string: address) { + if #available(iOS 16.0, *), let encodedHost = components.encodedHost { + if let decodedHost = components.host, encodedHost != decodedHost { + if decodedHost.lowercased().rangeOfCharacter(from: asciiChars) == nil { + address = address.replacingOccurrences(of: encodedHost, with: decodedHost) + } + } + } else if let encodedHost = components.host { + if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost { + if decodedHost.lowercased().rangeOfCharacter(from: asciiChars) == nil { + address = address.replacingOccurrences(of: encodedHost, with: decodedHost) + } + } + } + } + if trim { + address = address.replacingOccurrences(of: "https://www.", with: "") + address = address.replacingOccurrences(of: "https://", with: "") + address = address.replacingOccurrences(of: "tonsite://", with: "") + address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + } + return address + } +} diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index f86f73e0a5..4734a26665 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2796,9 +2796,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openUrl: { url, _, _, message in interaction.openUrl(url) }, openInstantPage: { [weak self] message, data in - if let (webpage, anchor) = instantPageAndAnchor(message: message) { - let pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: .channel), anchor: anchor) - self?.navigationController?.pushViewController(pageController) + if let self, let navigationController = self.navigationController { + if let controller = self.context.sharedContext.makeInstantPageController(context: self.context, message: message, sourcePeerType: .channel) { + navigationController.pushViewController(controller) + } } }, longTap: { action, message in }, getHiddenMedia: { diff --git a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift index 8ed99a1fb8..c802ca3928 100644 --- a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift +++ b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift @@ -433,7 +433,7 @@ public func chatTextLinkEditController(sharedContext: SharedAccountContext, upda return } let updatedLink = explicitUrl(contentNode.link) - if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false]) { + if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) { dismissImpl?(true) apply(updatedLink) } else if allowEmpty && contentNode.link.isEmpty { diff --git a/submodules/InstantPageUI/Sources/InstantPageController.swift b/submodules/InstantPageUI/Sources/InstantPageController.swift index a17188bf80..716dc6a464 100644 --- a/submodules/InstantPageUI/Sources/InstantPageController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageController.swift @@ -8,16 +8,6 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext -public struct InstantPageSourceLocation { - public var userLocation: MediaResourceUserLocation - public var peerType: MediaAutoDownloadPeerType - - public init(userLocation: MediaResourceUserLocation, peerType: MediaAutoDownloadPeerType) { - self.userLocation = userLocation - self.peerType = peerType - } -} - public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? { for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { diff --git a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift index 6c80171197..4885747d7f 100644 --- a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift +++ b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift @@ -5,6 +5,7 @@ import AccountContext import InstantPageUI import InstantPageCache import UrlHandling +import TelegramUIPreferences func faqSearchableItems(context: AccountContext, resolvedUrl: Signal, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> { let strings = context.sharedContext.currentPresentationData.with { $0 }.strings @@ -45,8 +46,9 @@ func faqSearchableItems(context: AccountContext, resolvedUrl: Signal ViewControl return controller } -private func cleanDomain(url: String) -> (domain: String, fullUrl: String) { - if let parsedUrl = URL(string: url) { - let host: String? - let scheme = parsedUrl.scheme ?? "https" - if #available(iOS 16.0, *) { - host = parsedUrl.host(percentEncoded: true)?.lowercased() - } else { - host = parsedUrl.host?.lowercased() - } - return (host ?? url, "\(scheme)://\(host ?? "")") - } else { - return (url, url) - } -} - private func fetchDomainExceptionInfo(context: AccountContext, url: String) -> Signal { let (domain, domainUrl) = cleanDomain(url: url) if #available(iOS 13.0, *), let url = URL(string: domainUrl) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 296abd0c85..d92634a4df 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -2326,7 +2326,7 @@ public final class BotPreviewStoryListContext: StoryListContext { guard let peer, let inputUser = apiInputUser(peer) else { return .single((nil, nil)) } - return _internal_requestBotPreview(network: account.network, peerId: peerId, inputUser: inputUser, language: language) + return _internal_requestBotAdminPreview(network: account.network, peerId: peerId, inputUser: inputUser, language: language) |> map { botPreview in return (botPreview, peer) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 86ebba237e..4a8b972ee1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -198,8 +198,12 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } let botPreview: Signal - if let user = maybePeer as? TelegramUser, let _ = user.botInfo { - botPreview = _internal_requestBotPreview(network: network, peerId: user.id, inputUser: inputUser, language: nil) + if let user = maybePeer as? TelegramUser, let botInfo = user.botInfo { + if botInfo.flags.contains(.canEdit) { + botPreview = _internal_requestBotAdminPreview(network: network, peerId: user.id, inputUser: inputUser, language: nil) + } else { + botPreview = _internal_requestBotUserPreview(network: network, peerId: user.id, inputUser: inputUser) + } } else { botPreview = .single(nil) } @@ -837,7 +841,7 @@ extension CachedPeerAutoremoveTimeout.Value { } } -func _internal_requestBotPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser, language: String?) -> Signal { +func _internal_requestBotAdminPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser, language: String?) -> Signal { return network.request(Api.functions.bots.getPreviewInfo(bot: inputUser, langCode: language ?? "")) |> map(Optional.init) |> `catch` { _ -> Signal in @@ -866,3 +870,30 @@ func _internal_requestBotPreview(network: Network, peerId: PeerId, inputUser: Ap } } } + +func _internal_requestBotUserPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser) -> Signal { + return network.request(Api.functions.bots.getPreviewMedias(bot: inputUser)) + |> map(Optional.init) + |> `catch` { _ -> Signal<[Api.BotPreviewMedia]?, NoError> in + return .single(nil) + } + |> map { result -> CachedUserData.BotPreview? in + guard let result else { + return nil + } + return CachedUserData.BotPreview( + items: result.compactMap { item -> CachedUserData.BotPreview.Item? in + switch item { + case let .botPreviewMedia(date, media): + let value = textMediaAndExpirationTimerFromApiMedia(media, peerId) + if let media = value.media { + return CachedUserData.BotPreview.Item(media: media, timestamp: date) + } else { + return nil + } + } + }, + alternativeLanguageCodes: [] + ) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD index e826f371a2..86a3e32102 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD @@ -51,6 +51,7 @@ swift_library( "//submodules/TextFormat", "//submodules/CounterControllerTitleView", "//submodules/TelegramUI/Components/AdminUserActionsSheet", + "//submodules/BrowserUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index b07d025dfc..d2a58e97ce 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -320,7 +320,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self?.openUrl(url.url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) + if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) { + navigationController.pushViewController(controller) + } } }, openWallpaper: { [weak self] message in if let strongSelf = self{ @@ -1223,8 +1225,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } case .chatFolder: break - case let .instantView(webpage, anchor): - strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel), anchor: anchor)) + case let .instantView(webPage, anchor): + let browserController = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel)) + strongSelf.pushController(browserController) case let .join(link): strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 7e99014b87..0adc9b1edd 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -5940,13 +5940,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } self.push(controller) - editCoverImpl = { [weak self, weak controller] in + editCoverImpl = { [weak self] in if let self { self.node.openCoverSelection(exclusive: false) } - if let controller { - controller.dismiss() - } } }) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 2ae84f8593..100fc0b918 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -3351,7 +3351,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if let foundGalleryMessage = foundGalleryMessage { - strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: foundGalleryMessage, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) + if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: foundGalleryMessage, sourcePeerType: associatedData?.automaticDownloadPeerType) { + navigationController.pushViewController(controller) + } } }, openWallpaper: { _ in }, openTheme: { _ in diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 42f157d5d9..e575681b3e 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1690,8 +1690,12 @@ final class ShareWithPeersScreenComponent: Component { title: item.title, image: item.image, hasNext: false, - action: { + action: { [weak self] in + guard let self, let component = self.component else { + return + } component.editCover() + self.saveAndDismiss() } )), environment: {}, @@ -1993,6 +1997,36 @@ final class ShareWithPeersScreenComponent: Component { private var currentHasChannels: Bool? private var currentHasCover: Bool? + func saveAndDismiss() { + guard let component = self.component, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else { + return + } + let base: EngineStoryPrivacy.Base + if self.selectedCategories.contains(.everyone) { + base = .everyone + } else if self.selectedCategories.contains(.closeFriends) { + base = .closeFriends + } else if self.selectedCategories.contains(.contacts) { + base = .contacts + } else if self.selectedCategories.contains(.selectedContacts) { + base = .nobody + } else { + base = .nobody + } + component.completion( + self.sendAsPeerId, + EngineStoryPrivacy( + base: base, + additionallyIncludePeers: self.selectedPeers + ), + self.selectedOptions.contains(.screenshot), + self.selectedOptions.contains(.pin), + self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? [], + false + ) + controller.requestDismiss() + } + func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { guard !self.isDismissed else { return availableSize @@ -2475,33 +2509,10 @@ final class ShareWithPeersScreenComponent: Component { component: AnyComponent(Button( content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), action: { [weak self] in - guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else { + guard let self else { return } - let base: EngineStoryPrivacy.Base - if self.selectedCategories.contains(.everyone) { - base = .everyone - } else if self.selectedCategories.contains(.closeFriends) { - base = .closeFriends - } else if self.selectedCategories.contains(.contacts) { - base = .contacts - } else if self.selectedCategories.contains(.selectedContacts) { - base = .nobody - } else { - base = .nobody - } - component.completion( - self.sendAsPeerId, - EngineStoryPrivacy( - base: base, - additionallyIncludePeers: self.selectedPeers - ), - self.selectedOptions.contains(.screenshot), - self.selectedOptions.contains(.pin), - self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? [], - false - ) - controller.requestDismiss() + self.saveAndDismiss() } ).minSize(CGSize(width: navigationHeight, height: navigationHeight))), environment: {}, diff --git a/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift b/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift index 4d77d7cc97..701b531c7f 100644 --- a/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift +++ b/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift @@ -368,7 +368,7 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) - let params = RippleParams(amplitude: 20.0, frequency: 15.0, decay: 8.0, speed: 1400.0) + let params = RippleParams(amplitude: 10.0, frequency: 15.0, decay: 8.0, speed: 1400.0) if let currentCloneView = self.currentCloneView { currentCloneView.removeFromSuperview() @@ -418,26 +418,6 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { self.contentNodeSource.clipsToBounds = true self.contentNodeSource.layer.cornerRadius = cornerRadius - /*let gradientLayer: SimpleGradientLayer - if let current = self.gradientLayer { - gradientLayer = current - } else { - gradientLayer = SimpleGradientLayer() - self.gradientLayer = gradientLayer - self.layer.addSublayer(gradientLayer) - - gradientLayer.type = .radial - gradientLayer.colors = [UIColor.clear.cgColor, UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor, UIColor.clear.cgColor] - } - gradientLayer.frame = CGRect(origin: CGPoint(), size: size) - - gradientLayer.startPoint = CGPoint(x: startPoint.x / size.width, y: startPoint.x / size.height) - let radius = CGSize(width: maxEdge, height: maxEdge) - let endEndPoint = CGPoint(x: (gradientLayer.startPoint.x + radius.width) * 1.0, y: (gradientLayer.startPoint.y + radius.height) * 1.0) - gradientLayer.endPoint = endEndPoint - - let progress = max(0.0, min(1.0, self.timeValue / maxDelay))*/ - #if DEBUG if let fpsView = self.fpsView { fpsView.update() @@ -456,6 +436,49 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { meshView.frame = CGRect(origin: CGPoint(), size: size) + if let shockwave = self.shockwaves.first { + let gradientLayer: SimpleGradientLayer + if let current = self.gradientLayer { + gradientLayer = current + } else { + gradientLayer = SimpleGradientLayer() + self.gradientLayer = gradientLayer + self.layer.addSublayer(gradientLayer) + + gradientLayer.type = .radial + //gradientLayer.colors = [UIColor.clear.cgColor, UIColor.clear.cgColor, UIColor.red.cgColor, UIColor.clear.cgColor] + gradientLayer.colors = [UIColor(white: 1.0, alpha: 0.0).cgColor, UIColor(white: 1.0, alpha: 0.0).cgColor, UIColor(white: 1.0, alpha: 0.2).cgColor, UIColor(white: 1.0, alpha: 0.0).cgColor] + } + gradientLayer.frame = CGRect(origin: CGPoint(), size: size) + + gradientLayer.startPoint = CGPoint(x: shockwave.startPoint.x / size.width, y: shockwave.startPoint.y / size.height) + + let distance = shockwave.timeValue * params.speed + let progress = max(0.0, distance / min(size.width, size.height)) + + let radius = CGSize(width: 1.0 * progress, height: (size.width / size.height) * progress) + let endEndPoint = CGPoint(x: (gradientLayer.startPoint.x + radius.width), y: (gradientLayer.startPoint.y + radius.height)) + gradientLayer.endPoint = endEndPoint + + let maxWavefrontNorm: CGFloat = 0.3 + + let normProgress = max(0.0, min(1.0, progress)) + let interpolatedNorm: CGFloat = 1.0 * (1.0 - normProgress) + maxWavefrontNorm * normProgress + let wavefrontNorm: CGFloat = max(0.01, min(0.99, interpolatedNorm)) + + gradientLayer.locations = ([0.0, 1.0 - wavefrontNorm, 1.0 - wavefrontNorm * 0.5, 1.0] as [CGFloat]).map { $0 as NSNumber } + + let alphaProgress: CGFloat = max(0.0, min(1.0, normProgress / 0.15)) + var interpolatedAlpha: CGFloat = alphaProgress + interpolatedAlpha = max(0.0, min(1.0, interpolatedAlpha)) + gradientLayer.opacity = Float(interpolatedAlpha) + } else { + if let gradientLayer = self.gradientLayer { + self.gradientLayer = nil + gradientLayer.removeFromSuperlayer() + } + } + let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y)) var instanceBounds: [CGRect] = [] diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 26b19cb9a1..2f21361397 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -6138,7 +6138,7 @@ public final class StoryItemSetContainerComponent: Component { self.openStoryEditing() }))) - if case .file = component.slice.item.storyItem.media { + if case .file = component.slice.item.storyItem.media, component.slice.item.storyItem.isPinned { items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_EditCover, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/EditCover"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in @@ -6336,7 +6336,7 @@ public final class StoryItemSetContainerComponent: Component { self.openStoryEditing() }))) - if case .file = component.slice.item.storyItem.media { + if case .file = component.slice.item.storyItem.media, component.slice.item.storyItem.isPinned { items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_EditCover, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/EditCover"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index b190c0cb86..5d89917c5c 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -402,156 +402,160 @@ public extension ChatControllerImpl { } } - func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) { + func presentBotApp(botApp: BotApp?, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) { guard let peerId = self.chatLocation.peerId else { return } self.attachmentController?.dismiss(animated: true, completion: nil) - let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in - guard let strongSelf = self else { - return - } - commit() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - return $0.updatedTitlePanelContext { - if !$0.contains(where: { - switch $0 { - case .requestInProgress: - return true - default: - return false - } - }) { - var updatedContexts = $0 - updatedContexts.append(.requestInProgress) - return updatedContexts.sorted() - } - return $0 - } - }) - - let updateProgress = { [weak self] in - Queue.mainQueue().async { - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - return $0.updatedTitlePanelContext { - if let index = $0.firstIndex(where: { - switch $0 { - case .requestInProgress: - return true - default: - return false - } - }) { - var updatedContexts = $0 - updatedContexts.remove(at: index) - return updatedContexts - } - return $0 - } - }) - } - } - } - - let botAddress = botPeer.addressName ?? "" - strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite) - |> afterDisposed { - updateProgress() - }) - |> deliverOnMainQueue).startStrict(next: { [weak self] result in + if let botApp { + let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in guard let strongSelf = self else { return } - let context = strongSelf.context - let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize)) - var presentImpl: ((ViewController, Any?) -> Void)? - let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in - ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in - presentImpl?(c, a) - }, commit: commit) - }, requestSwitchInline: { [weak self] query, chatTypes, completion in - ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) - }, completion: { [weak self] in - self?.chatDisplayNode.historyNode.scrollToEndOfHistory() - }, getNavigationController: { [weak self] in - return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController + commit() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + return $0.updatedTitlePanelContext { + if !$0.contains(where: { + switch $0 { + case .requestInProgress: + return true + default: + return false + } + }) { + var updatedContexts = $0 + updatedContexts.append(.requestInProgress) + return updatedContexts.sorted() + } + return $0 + } }) - controller.navigationPresentation = .flatModal - strongSelf.currentWebAppController = controller - strongSelf.push(controller) - presentImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) - } - - if justInstalled { - let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil) - controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) - } - }, 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 _ = combineLatest( - queue: Queue.mainQueue(), - ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id), - self.context.engine.messages.attachMenuBots(), - self.context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - ).startStandalone(next: { [weak self] noticed, attachMenuBots, attachMenuBot in - guard let self else { - return - } - - var isAttachMenuBotInstalled: Bool? - if let _ = attachMenuBot { - if let _ = attachMenuBots.first(where: { $0.peer.id == botPeer.id && !$0.flags.contains(.notActivated) }) { - isAttachMenuBotInstalled = true - } else { - isAttachMenuBotInstalled = false - } - } - - let context = self.context - if !noticed || botApp.flags.contains(.notActivated) || isAttachMenuBotInstalled == false { - if let isAttachMenuBotInstalled, let attachMenuBot { - if !isAttachMenuBotInstalled { - let controller = webAppTermsAlertController(context: context, updatedPresentationData: self.updatedPresentationData, bot: attachMenuBot, completion: { allowWrite in - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() - let _ = (context.engine.messages.addBotToAttachMenu(botId: botPeer.id, allowWrite: allowWrite) - |> deliverOnMainQueue).startStandalone(error: { _ in - }, completed: { - openBotApp(allowWrite, true) + let updateProgress = { [weak self] in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + return $0.updatedTitlePanelContext { + if let index = $0.firstIndex(where: { + switch $0 { + case .requestInProgress: + return true + default: + return false + } + }) { + var updatedContexts = $0 + updatedContexts.remove(at: index) + return updatedContexts + } + return $0 + } }) + } + } + } + + let botAddress = botPeer.addressName ?? "" + strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite) + |> afterDisposed { + updateProgress() + }) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let strongSelf = self else { + return + } + let context = strongSelf.context + let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize)) + var presentImpl: ((ViewController, Any?) -> Void)? + let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in + ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in + presentImpl?(c, a) + }, commit: commit) + }, requestSwitchInline: { [weak self] query, chatTypes, completion in + ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) + }, completion: { [weak self] in + self?.chatDisplayNode.historyNode.scrollToEndOfHistory() + }, getNavigationController: { [weak self] in + return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController + }) + controller.navigationPresentation = .flatModal + strongSelf.currentWebAppController = controller + strongSelf.push(controller) + + presentImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } + + if justInstalled { + let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil) + controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + } + }, 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 _ = combineLatest( + queue: Queue.mainQueue(), + ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id), + self.context.engine.messages.attachMenuBots(), + self.context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + ).startStandalone(next: { [weak self] noticed, attachMenuBots, attachMenuBot in + guard let self else { + return + } + + var isAttachMenuBotInstalled: Bool? + if let _ = attachMenuBot { + if let _ = attachMenuBots.first(where: { $0.peer.id == botPeer.id && !$0.flags.contains(.notActivated) }) { + isAttachMenuBotInstalled = true + } else { + isAttachMenuBotInstalled = false + } + } + + let context = self.context + if !noticed || botApp.flags.contains(.notActivated) || isAttachMenuBotInstalled == false { + if let isAttachMenuBotInstalled, let attachMenuBot { + if !isAttachMenuBotInstalled { + let controller = webAppTermsAlertController(context: context, updatedPresentationData: self.updatedPresentationData, bot: attachMenuBot, completion: { allowWrite in + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() + let _ = (context.engine.messages.addBotToAttachMenu(botId: botPeer.id, allowWrite: allowWrite) + |> deliverOnMainQueue).startStandalone(error: { _ in + }, completed: { + openBotApp(allowWrite, true) + }) + }) + self.present(controller, in: .window(.root)) + } else { + openBotApp(false, false) + } + } else { + let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in + let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() + openBotApp(allowWrite, false) + }, showMore: { [weak self] in + if let self { + self.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil) + } }) self.present(controller, in: .window(.root)) - } else { - openBotApp(false, false) } } else { - let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in - let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() - openBotApp(allowWrite, false) - }, showMore: { [weak self] in - if let self { - self.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil) - } - }) - self.present(controller, in: .window(.root)) + openBotApp(false, false) } - } else { - openBotApp(false, false) - } - }) + }) + } else { + self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, peer: botPeer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false) + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ee2d82c7b3..1722b18872 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2626,8 +2626,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { strongSelf.chatDisplayNode.dismissInput() - strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) - + if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) { + navigationController.pushViewController(controller) + } if case .overlay = strongSelf.presentationInterfaceState.mode { strongSelf.chatDisplayNode.dismissAsOverlay() } @@ -9305,10 +9306,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in if let strongSelf = self, let peer { - strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: { - dismissWebAppControllers() + if let botApp = botAppStart.botApp { + strongSelf.presentBotApp(botApp: botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: { + dismissWebAppControllers() + commit() + }) + } else { + strongSelf.context.sharedContext.openWebApp(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false) commit() - }) + } } }) default: @@ -9350,7 +9356,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G break default: progress?.set(.single(false)) - self.context.sharedContext.openChatInstantPage(context: self.context, message: message, sourcePeerType: nil, navigationController: navigationController) + if let controller = self.context.sharedContext.makeInstantPageController(context: self.context, message: message, sourcePeerType: nil) { + navigationController.pushViewController(controller) + } return } } diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index c0e002509a..cade6b3377 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -381,18 +381,16 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return false } -func openChatInstantPageImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { - if let (webpage, anchor) = instantPageAndAnchor(message: message) { - let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel) - - let pageController: ViewController - if context.sharedContext.immediateExperimentalUISettings.browserExperiment { - pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, anchor: anchor, sourceLocation: sourceLocation)) - } else { - pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor) - } - navigationController.pushViewController(pageController) +func makeInstantPageControllerImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? { + guard let (webpage, anchor) = instantPageAndAnchor(message: message) else { + return nil } + let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel) + return makeInstantPageControllerImpl(context: context, webPage: webpage, anchor: anchor, sourceLocation: sourceLocation) +} + +func makeInstantPageControllerImpl(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController { + return BrowserScreen(context: context, subject: .instantPage(webPage: webPage, anchor: anchor, sourceLocation: sourceLocation)) } func openChatWallpaperImpl(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) { diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 167e85acec..4a21ce561c 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -248,15 +248,10 @@ func openResolvedUrlImpl( } }) present(controller, nil) - case let .instantView(webpage, anchor): + case let .instantView(webPage, anchor): let sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel) - let pageController: ViewController - if context.sharedContext.immediateExperimentalUISettings.browserExperiment { - pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, anchor: anchor, sourceLocation: sourceLocation)) - } else { - pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor) - } - navigationController?.pushViewController(pageController) + let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation) + navigationController?.pushViewController(browserController) case let .join(link): dismissInput() diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 4897df2846..d4b2515af7 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1047,7 +1047,14 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur let _ = (settings |> deliverOnMainQueue).startStandalone(next: { settings in - if let defaultWebBrowser = settings.defaultWebBrowser, defaultWebBrowser != "inApp" { + var isTonSite = false + if let host = parsedUrl.host, host.lowercased().hasSuffix(".ton") { + isTonSite = true + } else if let scheme = parsedUrl.scheme, scheme.lowercased().hasPrefix("tonsite") { + isTonSite = true + } + + if let defaultWebBrowser = settings.defaultWebBrowser, defaultWebBrowser != "inApp" && !isTonSite { let openInOptions = availableOpenInOptions(context: context, item: .url(url: url)) if let option = openInOptions.first(where: { $0.identifier == settings.defaultWebBrowser }) { if case let .openUrl(openInUrl) = option.action() { @@ -1067,8 +1074,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur break } } - - if settings.defaultWebBrowser == nil && !isExceptedDomain { + + if (settings.defaultWebBrowser == nil && !isExceptedDomain) || isTonSite { let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString)) navigationController?.pushViewController(controller) } else { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 0c4101bd01..d040458dc2 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1846,8 +1846,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } - public func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { - openChatInstantPageImpl(context: context, message: message, sourcePeerType: sourcePeerType, navigationController: navigationController) + public func makeInstantPageController(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? { + return makeInstantPageControllerImpl(context: context, message: message, sourcePeerType: sourcePeerType) + } + + public func makeInstantPageController(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController { + return makeInstantPageControllerImpl(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation) } public func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) { diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index a450ca906a..291257a38b 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -735,8 +735,22 @@ public final class TelegramRootController: NavigationController, TelegramRootCon //Xcode 16 #if canImport(ContactProvider) extension MediaEditorScreen.Result: @retroactive MediaEditorScreenResult { + public var target: Stories.PendingTarget { + if let sendAsPeerId = self.options.sendAsPeerId { + return .peer(sendAsPeerId) + } else { + return .myStories + } + } } #else extension MediaEditorScreen.Result: MediaEditorScreenResult { + public var target: Stories.PendingTarget { + if let sendAsPeerId = self.options.sendAsPeerId { + return .peer(sendAsPeerId) + } else { + return .myStories + } + } } #endif diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index 91d11008b4..5c3d6a7f65 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -15,6 +15,7 @@ import JoinLinkPreviewUI import PresentationDataUtils import UrlWhitelist import UndoUI +import BrowserUI func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in @@ -87,8 +88,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n case let .stickerPack(name, _): let packReference: StickerPackReference = .name(name) controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) - case let .instantView(webpage, anchor): - (controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group), anchor: anchor)) + case let .instantView(webPage, anchor): + let sourceLocation = InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group) + let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation) + (controller.navigationController as? NavigationController)?.pushViewController(browserController, animated: true) case let .join(link): controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) diff --git a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift index 74c5eced05..5e2beb637c 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift @@ -491,6 +491,16 @@ public enum MediaAutoDownloadPeerType { case channel } +public struct InstantPageSourceLocation { + public var userLocation: MediaResourceUserLocation + public var peerType: MediaAutoDownloadPeerType + + public init(userLocation: MediaResourceUserLocation, peerType: MediaAutoDownloadPeerType) { + self.userLocation = userLocation + self.peerType = peerType + } +} + public func effectiveAutodownloadCategories(settings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) -> MediaAutoDownloadCategories { let connection = settings.connectionSettings(for: networkType) switch connection.preset { diff --git a/submodules/UrlEscaping/Sources/UrlEscaping.swift b/submodules/UrlEscaping/Sources/UrlEscaping.swift index 83a6fed01c..bbf09a6488 100644 --- a/submodules/UrlEscaping/Sources/UrlEscaping.swift +++ b/submodules/UrlEscaping/Sources/UrlEscaping.swift @@ -22,7 +22,7 @@ public extension CharacterSet { }() } -public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": true, "https": true]) -> Bool { +public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": true, "https": true, "tonsite": true]) -> Bool { if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), let scheme = url.scheme?.lowercased(), let requiresTopLevelDomain = validSchemes[scheme], let host = url.host, (!requiresTopLevelDomain || host.contains(".")) && url.user == nil { if requiresTopLevelDomain { let components = host.components(separatedBy: ".") @@ -39,8 +39,12 @@ public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": tr public func explicitUrl(_ url: String) -> String { var url = url - if !url.hasPrefix("http") && !url.hasPrefix("https") && url.range(of: "://") == nil { - url = "https://\(url)" + if !url.lowercased().hasPrefix("http:") && !url.lowercased().hasPrefix("https:") && !url.lowercased().hasPrefix("tonsite:") && url.range(of: "://") == nil { + if let parsedUrl = URL(string: "http://\(url)"), parsedUrl.host?.hasSuffix(".ton") == true { + url = "tonsite://\(url)" + } else { + url = "https://\(url)" + } } return url } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index af85edc262..bc5e329a88 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -292,7 +292,20 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) } } return .startAttach(peerName, value, choose) - } else if queryItem.name == "story" { + } else if queryItem.name == "startapp" { + var compact = false + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "mode", value == "compact" { + compact = true + break + } + } + } + } + return .peer(.name(peerName), .appStart("", queryItem.value, compact)) + } else if queryItem.name == "story" { if let id = Int32(value) { return .peer(.name(peerName), .story(id)) } @@ -321,6 +334,19 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) return .peer(.name(peerName), .boost) } else if queryItem.name == "profile" { return .peer(.name(peerName), .profile) + } else if queryItem.name == "startapp" { + var compact = false + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "mode", value == "compact" { + compact = true + break + } + } + } + } + return .peer(.name(peerName), .appStart("", nil, compact)) } } } @@ -720,18 +746,26 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } case let .appStart(name, payload, compact): - return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { botApp -> Signal in - if let botApp { - return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, compact: compact))))) + if name.isEmpty { + if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { + return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: nil, payload: payload, justInstalled: false, compact: compact))))) } else { return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) } - }) + } else { + return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { botApp -> Signal in + if let botApp { + return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, compact: compact))))) + } else { + return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) + } + }) + } case let .channelMessage(id, timecode): if case let .channel(channel) = peer, channel.flags.contains(.isForum) { let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) @@ -1299,3 +1333,18 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal (domain: String, fullUrl: String) { + if let parsedUrl = URL(string: url) { + let host: String? + let scheme = parsedUrl.scheme ?? "https" + if #available(iOS 16.0, *) { + host = parsedUrl.host(percentEncoded: true)?.lowercased() + } else { + host = parsedUrl.host?.lowercased() + } + return (host ?? url, "\(scheme)://\(host ?? "")") + } else { + return (url, url) + } +} diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index 70f24be4cf..19b80d186e 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -40,6 +40,7 @@ swift_library( "//submodules/ShareController", "//submodules/UndoUI", "//submodules/OverlayStatusController", + "//submodules/TelegramUIPreferences", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 9b32c4a186..954c427f96 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -29,6 +29,7 @@ import ShareController import UndoUI import AvatarNode import OverlayStatusController +import TelegramUIPreferences private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -333,9 +334,6 @@ public final class WebAppController: ViewController, AttachmentContainable { if let parsedUrl = URL(string: url) { self.webView?.load(URLRequest(url: parsedUrl)) } - - self.checkBotIdAndUrl(url) - if let keepAliveSignal = controller.keepAliveSignal { self.keepAliveDisposable = (keepAliveSignal |> deliverOnMainQueue).start(error: { [weak self] _ in @@ -355,7 +353,6 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let strongSelf = self else { return } - strongSelf.checkBotIdAndUrl(result.url) if let parsedUrl = URL(string: result.url) { strongSelf.queryId = result.queryId strongSelf.webView?.load(URLRequest(url: parsedUrl)) @@ -368,17 +365,16 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let self, let controller = self.controller else { return } - guard case let .peer(peer, params) = result, let peer, case let .withBotApp(appStart) = params else { + guard case let .peer(peer, params) = result, let peer, case let .withBotApp(appStart) = params, let botApp = appStart.botApp else { controller.dismiss() return } - let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: appStart.botApp.id, accessHash: appStart.botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true) + let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true) |> deliverOnMainQueue).startStandalone(next: { [weak self] result in guard let self, let parsedUrl = URL(string: result.url) else { return } - self.checkBotIdAndUrl(result.url) - self.controller?.titleView?.title = WebAppTitle(title: appStart.botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified) + self.controller?.titleView?.title = WebAppTitle(title: botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified) self.webView?.load(URLRequest(url: parsedUrl)) }) }) @@ -390,9 +386,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } strongSelf.queryId = result.queryId strongSelf.webView?.load(URLRequest(url: parsedUrl)) - - strongSelf.checkBotIdAndUrl(result.url) - + if let keepAliveSignal = result.keepAliveSignal { strongSelf.keepAliveDisposable = (keepAliveSignal |> deliverOnMainQueue).start(error: { [weak self] _ in @@ -412,14 +406,6 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - func checkBotIdAndUrl(_ url: String) { -// if url.hasPrefix("https://walletbot.me"), let botId = self.controller?.botId.id._internalGetInt64Value(), botId != 1985737506 { -// let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: "Bot id mismatch, please report steps to app developer", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { -// })]) -// self.controller?.present(alertController, in: .window(.root)) -// } - } - @objc fileprivate func mainButtonPressed() { if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled { return @@ -845,7 +831,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } switch result { case let .instantView(webPage, anchor): - let controller = InstantPageController(context: strongSelf.context, webPage: webPage, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .otherPrivate), anchor: anchor) + let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .otherPrivate)) strongSelf.controller?.getNavigationController()?.pushViewController(controller) default: strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) @@ -1164,15 +1150,7 @@ public final class WebAppController: ViewController, AttachmentContainable { transitionOut: nil ) let controller = self.context.sharedContext.makeStoryMediaEditorScreen(context: self.context, source: source, text: text, link: linkUrl.flatMap { ($0, linkName) }, completion: { result, commit in -// let targetPeerId: EnginePeer.Id - let target: Stories.PendingTarget -// if let sendAsPeerId = result.options.sendAsPeerId { -// target = .peer(sendAsPeerId) -// targetPeerId = sendAsPeerId -// } else { - target = .myStories -// targetPeerId = self.context.account.peerId -// } + let target: Stories.PendingTarget = result.target externalState.storyTarget = target if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {