mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into send-stars
This commit is contained in:
commit
b5549d74b0
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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: {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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: {},
|
||||||
|
@ -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] = []
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user