Merge branch 'master' into send-stars

This commit is contained in:
Isaac 2024-08-02 11:32:25 +08:00
commit b5549d74b0
36 changed files with 796 additions and 351 deletions

View File

@ -755,7 +755,7 @@ public class MediaEditorTransitionOutExternalState {
} }
public protocol MediaEditorScreenResult { public protocol MediaEditorScreenResult {
var target: Stories.PendingTarget { get }
} }
public protocol TelegramRootControllerInterface: NavigationController { public protocol TelegramRootControllerInterface: NavigationController {
@ -963,7 +963,8 @@ public protocol SharedAccountContext: AnyObject {
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void)
func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, 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 openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void)
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController

View File

@ -292,12 +292,12 @@ public struct ChatControllerInitialAttachBotStart {
} }
public struct ChatControllerInitialBotAppStart { public struct ChatControllerInitialBotAppStart {
public let botApp: BotApp public let botApp: BotApp?
public let payload: String? public let payload: String?
public let justInstalled: Bool public let justInstalled: Bool
public let compact: 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.botApp = botApp
self.payload = payload self.payload = payload
self.justInstalled = justInstalled self.justInstalled = justInstalled

View File

@ -281,15 +281,7 @@ final class AddressBarContentComponent: Component {
} }
let isActive = self.textField?.isFirstResponder ?? false let isActive = self.textField?.isFirstResponder ?? false
var title: String = "" let title = getDisplayUrl(component.url, hostOnly: true)
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
}
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) 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 return availableSize
@ -449,19 +441,7 @@ final class AddressBarContentComponent: Component {
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
} }
var address = self.component?.url ?? "" let address = getDisplayUrl(self.component?.url ?? "", trim: false)
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)
}
}
}
if textField.text != address { if textField.text != address {
textField.text = address textField.text = address
self.clearIconView.isHidden = address.isEmpty self.clearIconView.isHidden = address.isEmpty

View File

@ -176,23 +176,7 @@ final class BrowserAddressListItemComponent: Component {
if case let .Loaded(content) = component.webPage.content { if case let .Loaded(content) = component.webPage.content {
title = content.title ?? content.url title = content.title ?? content.url
var address = content.url subtitle = getDisplayUrl(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
parsedUrl = URL(string: content.url) parsedUrl = URL(string: content.url)

View File

@ -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.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.title = self.presentationData.strings.WebBrowser_Bookmarks_Title
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in

View File

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

View File

@ -384,13 +384,13 @@ private final class BrowserScreenComponent: CombinedComponent {
bottomInset: toolbarBottomInset, bottomInset: toolbarBottomInset,
sideInset: environment.safeInsets.left, sideInset: environment.safeInsets.left,
item: toolbarContent, item: toolbarContent,
collapseFraction: collapseFraction collapseFraction: 0.0
), ),
availableSize: context.availableSize, availableSize: context.availableSize,
transition: context.transition transition: context.transition
) )
context.add(toolbar 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 .appear(ComponentTransition.Appear { _, view, transition in
transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: view.frame.height), to: CGPoint(), additive: true) 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) { 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.context = context
self.subject = subject self.subject = subject
self.openPreviousOnClose = openPreviousOnClose self.openPreviousOnClose = openPreviousOnClose

View File

@ -239,12 +239,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
let request = URLRequest(url: parsedUrl) let request = URLRequest(url: parsedUrl)
self.webView.load(request) self.webView.load(request)
title = parsedUrl.host ?? "" title = getDisplayUrl(url, hostOnly: true)
} }
self.errorView = ComponentHostView() 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<BrowserContentState>(self._state) self.statePromise = Promise<BrowserContentState>(self._state)
super.init(frame: .zero) super.init(frame: .zero)
@ -725,7 +725,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
} }
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { 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) 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) { 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 self.currentError = error
} else { } else {
self.currentError = nil self.currentError = nil

View File

@ -108,3 +108,47 @@ func getPrimaryUrl(message: Message) -> String? {
} }
return primaryUrl 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
}
}

View File

@ -2796,9 +2796,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openUrl: { url, _, _, message in }, openUrl: { url, _, _, message in
interaction.openUrl(url) interaction.openUrl(url)
}, openInstantPage: { [weak self] message, data in }, openInstantPage: { [weak self] message, data in
if let (webpage, anchor) = instantPageAndAnchor(message: message) { if let self, let navigationController = self.navigationController {
let pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: .channel), anchor: anchor) if let controller = self.context.sharedContext.makeInstantPageController(context: self.context, message: message, sourcePeerType: .channel) {
self?.navigationController?.pushViewController(pageController) navigationController.pushViewController(controller)
}
} }
}, longTap: { action, message in }, longTap: { action, message in
}, getHiddenMedia: { }, getHiddenMedia: {

View File

@ -433,7 +433,7 @@ public func chatTextLinkEditController(sharedContext: SharedAccountContext, upda
return return
} }
let updatedLink = explicitUrl(contentNode.link) 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) dismissImpl?(true)
apply(updatedLink) apply(updatedLink)
} else if allowEmpty && contentNode.link.isEmpty { } else if allowEmpty && contentNode.link.isEmpty {

View File

@ -8,16 +8,6 @@ import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import AccountContext 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?)? { public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? {
for media in message.media { for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {

View File

@ -5,6 +5,7 @@ import AccountContext
import InstantPageUI import InstantPageUI
import InstantPageCache import InstantPageCache
import UrlHandling import UrlHandling
import TelegramUIPreferences
func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl?, NoError>, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> { func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl?, NoError>, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> {
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
@ -45,8 +46,9 @@ func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl
} else { } else {
nextIndex += 1 nextIndex += 1
} }
let item = SettingsSearchableItem(id: .faq(index), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, _, present in let item = SettingsSearchableItem(id: .faq(index), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [strings.SettingsSearch_FAQ, currentSection], present: { context, _, present in
present(.push, InstantPageController(context: context, webPage: webPage, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel), anchor: anchor)) let controller = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel))
present(.push, controller)
}) })
if index == 1 { if index == 1 {
results.insert(item, at: 0) results.insert(item, at: 0)

View File

@ -16,6 +16,7 @@ import WebKit
import LinkPresentation import LinkPresentation
import CoreServices import CoreServices
import PersistentStringHash import PersistentStringHash
import UrlHandling
private final class WebBrowserSettingsControllerArguments { private final class WebBrowserSettingsControllerArguments {
let context: AccountContext let context: AccountContext
@ -453,21 +454,6 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
return controller 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<WebBrowserException, NoError> { private func fetchDomainExceptionInfo(context: AccountContext, url: String) -> Signal<WebBrowserException, NoError> {
let (domain, domainUrl) = cleanDomain(url: url) let (domain, domainUrl) = cleanDomain(url: url)
if #available(iOS 13.0, *), let url = URL(string: domainUrl) { if #available(iOS 13.0, *), let url = URL(string: domainUrl) {

View File

@ -2326,7 +2326,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
guard let peer, let inputUser = apiInputUser(peer) else { guard let peer, let inputUser = apiInputUser(peer) else {
return .single((nil, nil)) 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 |> map { botPreview in
return (botPreview, peer) return (botPreview, peer)
} }

View File

@ -198,8 +198,12 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
} }
let botPreview: Signal<CachedUserData.BotPreview?, NoError> let botPreview: Signal<CachedUserData.BotPreview?, NoError>
if let user = maybePeer as? TelegramUser, let _ = user.botInfo { if let user = maybePeer as? TelegramUser, let botInfo = user.botInfo {
botPreview = _internal_requestBotPreview(network: network, peerId: user.id, inputUser: inputUser, language: nil) 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 { } else {
botPreview = .single(nil) botPreview = .single(nil)
} }
@ -837,7 +841,7 @@ extension CachedPeerAutoremoveTimeout.Value {
} }
} }
func _internal_requestBotPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser, language: String?) -> Signal<CachedUserData.BotPreview?, NoError> { func _internal_requestBotAdminPreview(network: Network, peerId: PeerId, inputUser: Api.InputUser, language: String?) -> Signal<CachedUserData.BotPreview?, NoError> {
return network.request(Api.functions.bots.getPreviewInfo(bot: inputUser, langCode: language ?? "")) return network.request(Api.functions.bots.getPreviewInfo(bot: inputUser, langCode: language ?? ""))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.bots.PreviewInfo?, NoError> in |> `catch` { _ -> Signal<Api.bots.PreviewInfo?, NoError> 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<CachedUserData.BotPreview?, NoError> {
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: []
)
}
}

View File

@ -51,6 +51,7 @@ swift_library(
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/CounterControllerTitleView", "//submodules/CounterControllerTitleView",
"//submodules/TelegramUI/Components/AdminUserActionsSheet", "//submodules/TelegramUI/Components/AdminUserActionsSheet",
"//submodules/BrowserUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -320,7 +320,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openUrl(url.url) self?.openUrl(url.url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { 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 }, openWallpaper: { [weak self] message in
if let strongSelf = self{ if let strongSelf = self{
@ -1223,8 +1225,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
} }
case .chatFolder: case .chatFolder:
break break
case let .instantView(webpage, anchor): case let .instantView(webPage, anchor):
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel), anchor: 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): case let .join(link):
strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in
if let strongSelf = self { if let strongSelf = self {

View File

@ -5940,13 +5940,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
self.push(controller) self.push(controller)
editCoverImpl = { [weak self, weak controller] in editCoverImpl = { [weak self] in
if let self { if let self {
self.node.openCoverSelection(exclusive: false) self.node.openCoverSelection(exclusive: false)
} }
if let controller {
controller.dismiss()
}
} }
}) })
} }

View File

@ -3351,7 +3351,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
if let foundGalleryMessage = foundGalleryMessage { 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 }, openWallpaper: { _ in
}, openTheme: { _ in }, openTheme: { _ in

View File

@ -1690,8 +1690,12 @@ final class ShareWithPeersScreenComponent: Component {
title: item.title, title: item.title,
image: item.image, image: item.image,
hasNext: false, hasNext: false,
action: { action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.editCover() component.editCover()
self.saveAndDismiss()
} }
)), )),
environment: {}, environment: {},
@ -1993,6 +1997,36 @@ final class ShareWithPeersScreenComponent: Component {
private var currentHasChannels: Bool? private var currentHasChannels: Bool?
private var currentHasCover: 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<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize { func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
guard !self.isDismissed else { guard !self.isDismissed else {
return availableSize return availableSize
@ -2475,33 +2509,10 @@ final class ShareWithPeersScreenComponent: Component {
component: AnyComponent(Button( component: AnyComponent(Button(
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
action: { [weak self] in action: { [weak self] in
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else { guard let self else {
return return
} }
let base: EngineStoryPrivacy.Base self.saveAndDismiss()
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()
} }
).minSize(CGSize(width: navigationHeight, height: navigationHeight))), ).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
environment: {}, environment: {},

View File

@ -368,7 +368,7 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) 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 { if let currentCloneView = self.currentCloneView {
currentCloneView.removeFromSuperview() currentCloneView.removeFromSuperview()
@ -418,26 +418,6 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
self.contentNodeSource.clipsToBounds = true self.contentNodeSource.clipsToBounds = true
self.contentNodeSource.layer.cornerRadius = cornerRadius 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 DEBUG
if let fpsView = self.fpsView { if let fpsView = self.fpsView {
fpsView.update() fpsView.update()
@ -456,6 +436,49 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
meshView.frame = CGRect(origin: CGPoint(), size: size) 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)) let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y))
var instanceBounds: [CGRect] = [] var instanceBounds: [CGRect] = []

View File

@ -6138,7 +6138,7 @@ public final class StoryItemSetContainerComponent: Component {
self.openStoryEditing() 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 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) return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/EditCover"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
@ -6336,7 +6336,7 @@ public final class StoryItemSetContainerComponent: Component {
self.openStoryEditing() 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 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) return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/EditCover"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in

View File

@ -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 { guard let peerId = self.chatLocation.peerId else {
return return
} }
self.attachmentController?.dismiss(animated: true, completion: nil) self.attachmentController?.dismiss(animated: true, completion: nil)
let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in if let botApp {
guard let strongSelf = self else { let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in
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
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let context = strongSelf.context commit()
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)? strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in return $0.updatedTitlePanelContext {
ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in if !$0.contains(where: {
presentImpl?(c, a) switch $0 {
}, commit: commit) case .requestInProgress:
}, requestSwitchInline: { [weak self] query, chatTypes, completion in return true
ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion) default:
}, completion: { [weak self] in return false
self?.chatDisplayNode.historyNode.scrollToEndOfHistory() }
}, getNavigationController: { [weak self] in }) {
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController 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 let updateProgress = { [weak self] in
controller?.present(c, in: .window(.root), with: a) Queue.mainQueue().async {
} if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
if justInstalled { return $0.updatedTitlePanelContext {
let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil) if let index = $0.firstIndex(where: {
controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) switch $0 {
} case .requestInProgress:
}, error: { [weak self] error in return true
if let strongSelf = self { default:
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: { return false
})]), in: .window(.root)) }
} }) {
})) var updatedContexts = $0
} updatedContexts.remove(at: index)
return updatedContexts
let _ = combineLatest( }
queue: Queue.mainQueue(), return $0
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<AttachMenuBot?, NoError> 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 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<AttachMenuBot?, NoError> 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)) self.present(controller, in: .window(.root))
} else {
openBotApp(false, false)
} }
} else { } else {
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in openBotApp(false, false)
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 {
} self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, peer: botPeer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false)
}) }
} }
} }

View File

@ -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) { if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) {
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
strongSelf.chatDisplayNode.dismissInput() 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 { if case .overlay = strongSelf.presentationInterfaceState.mode {
strongSelf.chatDisplayNode.dismissAsOverlay() 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)) let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
if let strongSelf = self, let peer { if let strongSelf = self, let peer {
strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: { if let botApp = botAppStart.botApp {
dismissWebAppControllers() 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() commit()
}) }
} }
}) })
default: default:
@ -9350,7 +9356,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break break
default: default:
progress?.set(.single(false)) 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 return
} }
} }

View File

@ -381,18 +381,16 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
return false return false
} }
func openChatInstantPageImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { func makeInstantPageControllerImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? {
if let (webpage, anchor) = instantPageAndAnchor(message: message) { guard let (webpage, anchor) = instantPageAndAnchor(message: message) else {
let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel) return nil
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 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) { func openChatWallpaperImpl(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {

View File

@ -248,15 +248,10 @@ func openResolvedUrlImpl(
} }
}) })
present(controller, nil) present(controller, nil)
case let .instantView(webpage, anchor): case let .instantView(webPage, anchor):
let sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel) let sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel)
let pageController: ViewController let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation)
if context.sharedContext.immediateExperimentalUISettings.browserExperiment { navigationController?.pushViewController(browserController)
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)
case let .join(link): case let .join(link):
dismissInput() dismissInput()

View File

@ -1047,7 +1047,14 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
let _ = (settings let _ = (settings
|> deliverOnMainQueue).startStandalone(next: { settings in |> 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)) let openInOptions = availableOpenInOptions(context: context, item: .url(url: url))
if let option = openInOptions.first(where: { $0.identifier == settings.defaultWebBrowser }) { if let option = openInOptions.first(where: { $0.identifier == settings.defaultWebBrowser }) {
if case let .openUrl(openInUrl) = option.action() { if case let .openUrl(openInUrl) = option.action() {
@ -1067,8 +1074,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
break break
} }
} }
if settings.defaultWebBrowser == nil && !isExceptedDomain { if (settings.defaultWebBrowser == nil && !isExceptedDomain) || isTonSite {
let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString)) let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString))
navigationController?.pushViewController(controller) navigationController?.pushViewController(controller)
} else { } else {

View File

@ -1846,8 +1846,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}) })
} }
public func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { public func makeInstantPageController(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? {
openChatInstantPageImpl(context: context, message: message, sourcePeerType: sourcePeerType, navigationController: navigationController) 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) { public func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {

View File

@ -735,8 +735,22 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
//Xcode 16 //Xcode 16
#if canImport(ContactProvider) #if canImport(ContactProvider)
extension MediaEditorScreen.Result: @retroactive MediaEditorScreenResult { extension MediaEditorScreen.Result: @retroactive MediaEditorScreenResult {
public var target: Stories.PendingTarget {
if let sendAsPeerId = self.options.sendAsPeerId {
return .peer(sendAsPeerId)
} else {
return .myStories
}
}
} }
#else #else
extension MediaEditorScreen.Result: MediaEditorScreenResult { extension MediaEditorScreen.Result: MediaEditorScreenResult {
public var target: Stories.PendingTarget {
if let sendAsPeerId = self.options.sendAsPeerId {
return .peer(sendAsPeerId)
} else {
return .myStories
}
}
} }
#endif #endif

View File

@ -15,6 +15,7 @@ import JoinLinkPreviewUI
import PresentationDataUtils import PresentationDataUtils
import UrlWhitelist import UrlWhitelist
import UndoUI import UndoUI
import BrowserUI
func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) {
let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in
@ -87,8 +88,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
case let .stickerPack(name, _): case let .stickerPack(name, _):
let packReference: StickerPackReference = .name(name) let packReference: StickerPackReference = .name(name)
controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
case let .instantView(webpage, anchor): 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)) 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): case let .join(link):
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))

View File

@ -491,6 +491,16 @@ public enum MediaAutoDownloadPeerType {
case channel 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 { public func effectiveAutodownloadCategories(settings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) -> MediaAutoDownloadCategories {
let connection = settings.connectionSettings(for: networkType) let connection = settings.connectionSettings(for: networkType)
switch connection.preset { switch connection.preset {

View File

@ -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 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 { if requiresTopLevelDomain {
let components = host.components(separatedBy: ".") 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 { public func explicitUrl(_ url: String) -> String {
var url = url var url = url
if !url.hasPrefix("http") && !url.hasPrefix("https") && url.range(of: "://") == nil { if !url.lowercased().hasPrefix("http:") && !url.lowercased().hasPrefix("https:") && !url.lowercased().hasPrefix("tonsite:") && url.range(of: "://") == nil {
url = "https://\(url)" if let parsedUrl = URL(string: "http://\(url)"), parsedUrl.host?.hasSuffix(".ton") == true {
url = "tonsite://\(url)"
} else {
url = "https://\(url)"
}
} }
return url return url
} }

View File

@ -292,7 +292,20 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
} }
} }
return .startAttach(peerName, value, choose) 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) { if let id = Int32(value) {
return .peer(.name(peerName), .story(id)) return .peer(.name(peerName), .story(id))
} }
@ -321,6 +334,19 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
return .peer(.name(peerName), .boost) return .peer(.name(peerName), .boost)
} else if queryItem.name == "profile" { } else if queryItem.name == "profile" {
return .peer(.name(peerName), .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): case let .appStart(name, payload, compact):
return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false) if name.isEmpty {
|> map(Optional.init) if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
|> `catch` { _ -> Signal<BotApp?, NoError> in return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: nil, payload: payload, justInstalled: false, compact: compact)))))
return .single(nil)
}
|> mapToSignal { botApp -> Signal<ResolveInternalUrlResult, NoError> in
if let botApp {
return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: botApp, payload: payload, justInstalled: false, compact: compact)))))
} else { } else {
return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)))) 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<BotApp?, NoError> in
return .single(nil)
}
|> mapToSignal { botApp -> Signal<ResolveInternalUrlResult, NoError> 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): case let .channelMessage(id, timecode):
if case let .channel(channel) = peer, channel.flags.contains(.isForum) { if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id)
@ -1299,3 +1333,18 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal<Resol
} }
} }
} }
public 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)
}
}

View File

@ -40,6 +40,7 @@ swift_library(
"//submodules/ShareController", "//submodules/ShareController",
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/OverlayStatusController", "//submodules/OverlayStatusController",
"//submodules/TelegramUIPreferences",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -29,6 +29,7 @@ import ShareController
import UndoUI import UndoUI
import AvatarNode import AvatarNode
import OverlayStatusController import OverlayStatusController
import TelegramUIPreferences
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
@ -333,9 +334,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let parsedUrl = URL(string: url) { if let parsedUrl = URL(string: url) {
self.webView?.load(URLRequest(url: parsedUrl)) self.webView?.load(URLRequest(url: parsedUrl))
} }
self.checkBotIdAndUrl(url)
if let keepAliveSignal = controller.keepAliveSignal { if let keepAliveSignal = controller.keepAliveSignal {
self.keepAliveDisposable = (keepAliveSignal self.keepAliveDisposable = (keepAliveSignal
|> deliverOnMainQueue).start(error: { [weak self] _ in |> deliverOnMainQueue).start(error: { [weak self] _ in
@ -355,7 +353,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.checkBotIdAndUrl(result.url)
if let parsedUrl = URL(string: result.url) { if let parsedUrl = URL(string: result.url) {
strongSelf.queryId = result.queryId strongSelf.queryId = result.queryId
strongSelf.webView?.load(URLRequest(url: parsedUrl)) strongSelf.webView?.load(URLRequest(url: parsedUrl))
@ -368,17 +365,16 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let self, let controller = self.controller else { guard let self, let controller = self.controller else {
return 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() controller.dismiss()
return 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 |> deliverOnMainQueue).startStandalone(next: { [weak self] result in
guard let self, let parsedUrl = URL(string: result.url) else { guard let self, let parsedUrl = URL(string: result.url) else {
return return
} }
self.checkBotIdAndUrl(result.url) self.controller?.titleView?.title = WebAppTitle(title: botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified)
self.controller?.titleView?.title = WebAppTitle(title: appStart.botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified)
self.webView?.load(URLRequest(url: parsedUrl)) self.webView?.load(URLRequest(url: parsedUrl))
}) })
}) })
@ -390,9 +386,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
strongSelf.queryId = result.queryId strongSelf.queryId = result.queryId
strongSelf.webView?.load(URLRequest(url: parsedUrl)) strongSelf.webView?.load(URLRequest(url: parsedUrl))
strongSelf.checkBotIdAndUrl(result.url)
if let keepAliveSignal = result.keepAliveSignal { if let keepAliveSignal = result.keepAliveSignal {
strongSelf.keepAliveDisposable = (keepAliveSignal strongSelf.keepAliveDisposable = (keepAliveSignal
|> deliverOnMainQueue).start(error: { [weak self] _ in |> 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() { @objc fileprivate func mainButtonPressed() {
if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled { if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled {
return return
@ -845,7 +831,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
switch result { switch result {
case let .instantView(webPage, anchor): 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) strongSelf.controller?.getNavigationController()?.pushViewController(controller)
default: default:
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) 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 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 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 = result.target
let target: Stories.PendingTarget
// if let sendAsPeerId = result.options.sendAsPeerId {
// target = .peer(sendAsPeerId)
// targetPeerId = sendAsPeerId
// } else {
target = .myStories
// targetPeerId = self.context.account.peerId
// }
externalState.storyTarget = target externalState.storyTarget = target
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {