Various fixes

This commit is contained in:
Ilya Laktyushin 2024-08-02 23:29:14 +02:00
parent 4a44c61c8d
commit aff6322def
62 changed files with 1386 additions and 819 deletions

View file

@ -305,6 +305,7 @@ public enum ResolvedUrl {
case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?)
case invoice(slug: String, invoice: TelegramMediaInvoice?)
case premiumOffer(reference: String?)
case starsTopup(amount: Int64?)
case chatFolder(slug: String)
case story(peerId: PeerId, id: Int32)
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
@ -755,7 +756,7 @@ public class MediaEditorTransitionOutExternalState {
}
public protocol MediaEditorScreenResult {
var target: Stories.PendingTarget { get }
}
public protocol TelegramRootControllerInterface: NavigationController {
@ -963,7 +964,8 @@ public protocol SharedAccountContext: AnyObject {
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void)
func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<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 makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController

View file

@ -292,12 +292,12 @@ public struct ChatControllerInitialAttachBotStart {
}
public struct ChatControllerInitialBotAppStart {
public let botApp: BotApp
public let botApp: BotApp?
public let payload: String?
public let justInstalled: Bool
public let compact: Bool
public init(botApp: BotApp, payload: String?, justInstalled: Bool, compact: Bool) {
public init(botApp: BotApp?, payload: String?, justInstalled: Bool, compact: Bool) {
self.botApp = botApp
self.payload = payload
self.justInstalled = justInstalled

View file

@ -123,7 +123,7 @@ public enum BoostSubject: Equatable {
}
public enum StarsPurchasePurpose: Equatable {
case generic
case generic(requiredStars: Int64?)
case transfer(peerId: EnginePeer.Id, requiredStars: Int64)
case subscription(peerId: EnginePeer.Id, requiredStars: Int64, renew: Bool)
case gift(peerId: EnginePeer.Id)

View file

@ -653,7 +653,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index
strongSelf.currentFrameCount = frame.totalFrames;
strongSelf.currentFrameCount = frame.totalFrames
strongSelf.currentFrameRate = frameRate
if frame.isLastFrame {

View file

@ -713,7 +713,7 @@ public class AttachmentController: ViewController, MinimizableController {
}
if case .ended = recognizer.state {
if let lastController = self.currentControllers.last {
if let controller = self.controller, controller.shouldMinimizeOnSwipe?(self.currentType) == true {
if let controller = self.controller, let layout = self.validLayout, !layout.metrics.isTablet, controller.shouldMinimizeOnSwipe?(self.currentType) == true {
self.minimize()
return
}

View file

@ -48,6 +48,7 @@ swift_library(
"//submodules/SearchBarNode",
"//submodules/TelegramUI/Components/SaveProgressScreen",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/Utils/DeviceModel",
],
visibility = [
"//visibility:public",

View file

@ -281,15 +281,7 @@ final class AddressBarContentComponent: Component {
}
let isActive = self.textField?.isFirstResponder ?? false
var title: String = ""
if let parsedUrl = URL(string: component.url) {
title = parsedUrl.host ?? component.url
if title.hasPrefix("www.") {
title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4))
}
title = title.idnaDecoded ?? title
}
let title = getDisplayUrl(component.url, hostOnly: true)
self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition)
return availableSize
@ -449,19 +441,7 @@ final class AddressBarContentComponent: Component {
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
}
var address = self.component?.url ?? ""
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
let address = getDisplayUrl(self.component?.url ?? "", trim: false)
if textField.text != address {
textField.text = address
self.clearIconView.isHidden = address.isEmpty

View file

@ -176,23 +176,7 @@ final class BrowserAddressListItemComponent: Component {
if case let .Loaded(content) = component.webPage.content {
title = content.title ?? content.url
var address = content.url
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
address = address.replacingOccurrences(of: "https://www.", with: "")
address = address.replacingOccurrences(of: "https://", with: "")
address = address.replacingOccurrences(of: "tonsite://", with: "")
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
subtitle = address
subtitle = getDisplayUrl(content.url)
parsedUrl = URL(string: content.url)

View file

@ -367,7 +367,7 @@ public final class BrowserBookmarksScreen: ViewController {
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
self.title = self.presentationData.strings.WebBrowser_Bookmarks_Title
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in

View file

@ -0,0 +1,299 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import PhotoResources
import CheckNode
import Markdown
private let textFont = Font.regular(13.0)
private let boldTextFont = Font.semibold(13.0)
private func formattedText(_ text: String, color: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString {
return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: color), linkAttribute: { _ in return nil}), textAlignment: textAlignment)
}
private final class BrowserExceptionDomainAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let domain: String
private let titleNode: ASTextNode
private let textNode: ASTextNode
private let allowWriteCheckNode: InteractiveCheckNode
private let allowWriteLabelNode: ASTextNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
private var iconDisposable: Disposable?
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
var allowWriteAccess: Bool = true {
didSet {
self.allowWriteCheckNode.setSelected(self.allowWriteAccess, animated: true)
}
}
init(account: Account, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, domain: String, requestWriteAccess: Bool, actions: [TextAlertAction]) {
self.strings = strings
self.domain = domain
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 0
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 0
self.allowWriteCheckNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false))
self.allowWriteCheckNode.setSelected(true, animated: false)
self.allowWriteLabelNode = ASTextNode()
self.allowWriteLabelNode.maximumNumberOfLines = 4
self.allowWriteLabelNode.isUserInteractionEnabled = true
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
return TextAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
if requestWriteAccess {
self.addSubnode(self.allowWriteCheckNode)
self.addSubnode(self.allowWriteLabelNode)
}
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.allowWriteCheckNode.valueChanged = { [weak self] value in
if let strongSelf = self {
strongSelf.allowWriteAccess = !strongSelf.allowWriteAccess
}
}
self.updateTheme(theme)
}
deinit {
self.iconDisposable?.dispose()
}
override func didLoad() {
super.didLoad()
self.allowWriteLabelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.allowWriteTap(_:))))
}
@objc private func allowWriteTap(_ gestureRecognizer: UITapGestureRecognizer) {
if self.allowWriteCheckNode.isUserInteractionEnabled {
self.allowWriteAccess = !self.allowWriteAccess
}
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: "Open in Browser", font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: "Do you want to open this link in your default browser?", font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.allowWriteLabelNode.attributedText = formattedText("Always open links from **\(self.domain)** in browser", color: theme.primaryColor)
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width , 270.0)
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let titleSize = self.titleNode.measure(size)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
origin.y += titleSize.height + 13.0
let textSize = self.textNode.measure(size)
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)
origin.y += textSize.height
var entriesHeight: CGFloat = 0.0
if self.allowWriteLabelNode.supernode != nil {
origin.y += 16.0
entriesHeight += 16.0
let checkSize = CGSize(width: 22.0, height: 22.0)
let condensedSize = CGSize(width: size.width - 76.0, height: size.height)
let allowWriteSize = self.allowWriteLabelNode.measure(condensedSize)
transition.updateFrame(node: self.allowWriteLabelNode, frame: CGRect(origin: CGPoint(x: 46.0, y: origin.y), size: allowWriteSize))
transition.updateFrame(node: self.allowWriteCheckNode, frame: CGRect(origin: CGPoint(x: 12.0, y: origin.y - 2.0), size: checkSize))
origin.y += allowWriteSize.height
entriesHeight += allowWriteSize.height
}
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var contentWidth = max(textSize.width, minActionsWidth)
contentWidth = max(contentWidth, 234.0)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultWidth = contentWidth + insets.left + insets.right
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + entriesHeight + actionsHeight + 17.0 + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
textFrame.origin.x = floorToScreenPixels((resultSize.width - textFrame.width) / 2.0)
transition.updateFrame(node: self.textNode, frame: textFrame)
return resultSize
}
}
public func browserExceptionDomainAlertController(context: AccountContext, domain: String, completion: @escaping (Bool) -> Void) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme
let strings = presentationData.strings
var dismissImpl: ((Bool) -> Void)?
var getContentNodeImpl: (() -> BrowserExceptionDomainAlertContentNode?)?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: "Continue", action: {
if let allowWriteAccess = getContentNodeImpl?()?.allowWriteAccess {
completion(allowWriteAccess)
} else {
completion(false)
}
dismissImpl?(true)
})]
let contentNode = BrowserExceptionDomainAlertContentNode(account: context.account, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, domain: domain, requestWriteAccess: true, actions: actions)
getContentNodeImpl = { [weak contentNode] in
return contentNode
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
}

View file

@ -414,8 +414,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
if let state = self.initialState {
didSetScrollOffset = true
contentOffset = CGPoint(x: 0.0, y: CGFloat(state.contentOffset))
}
else if let anchor = self.initialAnchor, !anchor.isEmpty {
} else if let anchor = self.initialAnchor, !anchor.isEmpty {
self.initialAnchor = nil
if let items = self.currentLayout?.items {
didSetScrollOffset = true
@ -1312,35 +1311,35 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
// guard let theme = self.theme, let webPage = self.webPage else {
// return
// }
//
// var targetAnchor: InstantPageTextAnchorItem?
// for (name, (line, _)) in item.anchors {
// if name == referenceAnchor {
// let anchors = item.lines[line].anchorItems
// for anchor in anchors {
// if anchor.name == referenceAnchor {
// targetAnchor = anchor
// break
// }
// }
// }
// }
//
// guard let anchorText = targetAnchor?.anchorText else {
// return
// }
//
// let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in
// self?.openUrl(url)
// }, openUrlIn: { [weak self] url in
// self?.openUrlIn(url)
// }, present: { [weak self] c, a in
// self?.present(c, a)
// })
// self.present(controller, nil)
guard let webPage = self.webPage else {
return
}
var targetAnchor: InstantPageTextAnchorItem?
for (name, (line, _)) in item.anchors {
if name == referenceAnchor {
let anchors = item.lines[line].anchorItems
for anchor in anchors {
if anchor.name == referenceAnchor {
targetAnchor = anchor
break
}
}
}
}
guard let anchorText = targetAnchor?.anchorText else {
return
}
let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in
self?.openUrl(url)
}, openUrlIn: { [weak self] url in
self?.openUrlIn(url)
}, present: { [weak self] c, a in
self?.present(c, a)
})
self.present(controller, nil)
}
private func scrollToAnchor(_ anchor: String) {

View file

@ -18,6 +18,7 @@ import MinimizedContainer
import InstantPageUI
import NavigationStackComponent
import LottieComponent
import WebKit
private let settingsTag = GenericComponentViewTag()
@ -384,13 +385,13 @@ private final class BrowserScreenComponent: CombinedComponent {
bottomInset: toolbarBottomInset,
sideInset: environment.safeInsets.left,
item: toolbarContent,
collapseFraction: collapseFraction
collapseFraction: 0.0
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(toolbar
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0 + toolbar.size.height * collapseFraction))
.appear(ComponentTransition.Appear { _, view, transition in
transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: view.frame.height), to: CGPoint(), additive: true)
})
@ -489,13 +490,13 @@ public class BrowserScreen: ViewController, MinimizableController {
case expand
}
fileprivate final class Node: ViewControllerTracingNode {
final class Node: ViewControllerTracingNode {
private weak var controller: BrowserScreen?
private let context: AccountContext
private let contentContainerView = UIView()
fileprivate let contentNavigationContainer = ComponentView<Empty>()
fileprivate var content: [BrowserContent] = []
private(set) var content: [BrowserContent] = []
fileprivate var contentState: BrowserContentState?
private var contentStateDisposable = MetaDisposable()
@ -785,13 +786,14 @@ public class BrowserScreen: ViewController, MinimizableController {
let browserContent: BrowserContent
switch content {
case let .webPage(url):
let webContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url)
let webContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url, preferredConfiguration: self.controller?.preferredConfiguration)
webContent.cancelInteractiveTransitionGestures = { [weak self] in
if let self, let view = self.controller?.view {
cancelInteractiveTransitionGestures(view: view)
}
}
browserContent = webContent
self.controller?.preferredConfiguration = nil
case let .instantPage(webPage, anchor, sourceLocation):
let instantPageContent = BrowserInstantPageContent(context: self.context, presentationData: self.presentationData, webPage: webPage, anchor: anchor, url: webPage.content.url ?? "", sourceLocation: sourceLocation)
instantPageContent.openPeer = { [weak self] peer in
@ -846,7 +848,9 @@ public class BrowserScreen: ViewController, MinimizableController {
return
}
if controller.isMinimized {
if let navigationController = controller.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
minimizedContainer.removeController(controller)
}
} else {
controller.dismiss()
}
@ -1328,7 +1332,8 @@ public class BrowserScreen: ViewController, MinimizableController {
right: layout.safeInsets.right
),
navigationBarHeight: navigationBarHeight,
scrollingPanelOffsetFraction: self.scrollingPanelOffsetFraction
scrollingPanelOffsetFraction: self.scrollingPanelOffsetFraction,
hasBottomPanel: !layout.metrics.isTablet || self.presentationState.isSearching
)
))
)
@ -1372,7 +1377,7 @@ public class BrowserScreen: ViewController, MinimizableController {
private let context: AccountContext
private let subject: Subject
private var preferredConfiguration: WKWebViewConfiguration?
private var openPreviousOnClose = false
private var validLayout: ContainerViewLayout?
@ -1389,9 +1394,20 @@ public class BrowserScreen: ViewController, MinimizableController {
// "application/vnd.openxmlformats-officedocument.presentationml.presentation"
]
public init(context: AccountContext, subject: Subject, openPreviousOnClose: Bool = false) {
public init(context: AccountContext, subject: Subject, preferredConfiguration: WKWebViewConfiguration? = nil, openPreviousOnClose: Bool = false) {
var subject = subject
if case let .webPage(url) = subject, let parsedUrl = URL(string: url) {
if parsedUrl.host?.hasSuffix(".ton") == true {
var urlComponents = URLComponents(string: url)
urlComponents?.scheme = "tonsite"
if let updatedUrl = urlComponents?.url?.absoluteString {
subject = .webPage(url: updatedUrl)
}
}
}
self.context = context
self.subject = subject
self.preferredConfiguration = preferredConfiguration
self.openPreviousOnClose = openPreviousOnClose
super.init(navigationBarPresentationData: nil)
@ -1409,7 +1425,7 @@ public class BrowserScreen: ViewController, MinimizableController {
preconditionFailure()
}
private var node: Node {
var node: Node {
return self.displayNode as! Node
}
@ -1529,17 +1545,20 @@ private final class BrowserContentComponent: Component {
let insets: UIEdgeInsets
let navigationBarHeight: CGFloat
let scrollingPanelOffsetFraction: CGFloat
let hasBottomPanel: Bool
init(
content: BrowserContent,
insets: UIEdgeInsets,
navigationBarHeight: CGFloat,
scrollingPanelOffsetFraction: CGFloat
scrollingPanelOffsetFraction: CGFloat,
hasBottomPanel: Bool
) {
self.content = content
self.insets = insets
self.navigationBarHeight = navigationBarHeight
self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
self.hasBottomPanel = hasBottomPanel
}
static func ==(lhs: BrowserContentComponent, rhs: BrowserContentComponent) -> Bool {
@ -1555,6 +1574,9 @@ private final class BrowserContentComponent: Component {
if lhs.scrollingPanelOffsetFraction != rhs.scrollingPanelOffsetFraction {
return false
}
if lhs.hasBottomPanel != rhs.hasBottomPanel {
return false
}
return true
}
@ -1574,9 +1596,9 @@ private final class BrowserContentComponent: Component {
let collapsedHeight: CGFloat = 24.0
let topInset: CGFloat = component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + (component.insets.top + collapsedHeight) * component.scrollingPanelOffsetFraction
let bottomInset = (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction)
let bottomInset = component.hasBottomPanel ? (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction) : 0.0
let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right)
let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: 49.0 + component.insets.bottom, right: component.insets.right)
let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: component.hasBottomPanel ? 49.0 + component.insets.bottom : 0.0, right: component.insets.right)
component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, safeInsets: component.insets, transition: transition)
transition.setFrame(view: component.content, frame: CGRect(origin: .zero, size: availableSize))

View file

@ -20,6 +20,7 @@ import MultilineTextComponent
import UrlEscaping
import UrlHandling
import SaveProgressScreen
import DeviceModel
private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
private final class PendingTask {
@ -151,6 +152,22 @@ private class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {
}
}
private func computedUserAgent() -> String {
func getFirmwareVersion() -> String? {
var size = 0
sysctlbyname("kern.osversion", nil, &size, nil, 0)
var str = [CChar](repeating: 0, count: size)
sysctlbyname("kern.osversion", &str, &size, nil, 0)
return String(cString: str)
}
let osVersion = UIDevice.current.systemVersion
let firmwareVersion = getFirmwareVersion() ?? "15E148"
return DeviceModel.current.isIpad ? "Version/\(osVersion) Safari/605.1.15" : "Version/\(osVersion) Mobile/\(firmwareVersion) Safari/604.1"
}
final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
@ -186,42 +203,49 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
private var tempFile: TempBoxFile?
init(context: AccountContext, presentationData: PresentationData, url: String) {
init(context: AccountContext, presentationData: PresentationData, url: String, preferredConfiguration: WKWebViewConfiguration? = nil) {
self.context = context
self.uuid = UUID()
self.presentationData = presentationData
let configuration = WKWebViewConfiguration()
var proxyServerHost = "magic.org"
if let data = context.currentAppConfiguration.with({ $0 }).data, let hostValue = data["ton_proxy_address"] as? String {
proxyServerHost = hostValue
}
configuration.setURLSchemeHandler(TonSchemeHandler(proxyServerHost: proxyServerHost), forURLScheme: "tonsite")
configuration.allowsInlineMediaPlayback = true
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
configuration.mediaTypesRequiringUserActionForPlayback = []
} else {
configuration.mediaPlaybackRequiresUserAction = false
}
let contentController = WKUserContentController()
let videoScript = WKUserScript(source: videoSource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(videoScript)
let touchScript = WKUserScript(source: setupTouchObservers, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(touchScript)
configuration.userContentController = contentController
var handleScriptMessageImpl: ((WKScriptMessage) -> Void)?
let eventProxyScript = WKUserScript(source: eventProxySource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(eventProxyScript)
contentController.add(WeakScriptMessageHandler { message in
handleScriptMessageImpl?(message)
}, name: "performAction")
let configuration: WKWebViewConfiguration
if let preferredConfiguration {
configuration = preferredConfiguration
} else {
configuration = WKWebViewConfiguration()
var proxyServerHost = "magic.org"
if let data = context.currentAppConfiguration.with({ $0 }).data, let hostValue = data["ton_proxy_address"] as? String {
proxyServerHost = hostValue
}
configuration.setURLSchemeHandler(TonSchemeHandler(proxyServerHost: proxyServerHost), forURLScheme: "tonsite")
configuration.allowsInlineMediaPlayback = true
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
configuration.mediaTypesRequiringUserActionForPlayback = []
} else {
configuration.mediaPlaybackRequiresUserAction = false
}
let contentController = WKUserContentController()
let videoScript = WKUserScript(source: videoSource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(videoScript)
let touchScript = WKUserScript(source: setupTouchObservers, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(touchScript)
let eventProxyScript = WKUserScript(source: eventProxySource, injectionTime: .atDocumentStart, forMainFrameOnly: false)
contentController.addUserScript(eventProxyScript)
contentController.add(WeakScriptMessageHandler { message in
handleScriptMessageImpl?(message)
}, name: "performAction")
configuration.userContentController = contentController
configuration.applicationNameForUserAgent = computedUserAgent()
}
self.webView = WebView(frame: CGRect(), configuration: configuration)
self.webView.allowsLinkPreview = true
if #available(iOS 11.0, *) {
self.webView.scrollView.contentInsetAdjustmentBehavior = .never
}
@ -239,18 +263,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
let request = URLRequest(url: parsedUrl)
self.webView.load(request)
title = parsedUrl.host ?? ""
title = getDisplayUrl(url, hostOnly: true)
}
self.errorView = ComponentHostView()
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, readingProgress: 0.0, contentType: .webPage)
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.1, readingProgress: 0.0, contentType: .webPage)
self.statePromise = Promise<BrowserContentState>(self._state)
super.init(frame: .zero)
self.backgroundColor = presentationData.theme.list.plainBackgroundColor
self.webView.backgroundColor = presentationData.theme.list.plainBackgroundColor
self.webView.isOpaque = false
self.webView.allowsBackForwardNavigationGestures = true
self.webView.scrollView.delegate = self
@ -528,7 +553,14 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top))
let currentBounds = self.webView.scrollView.bounds
let offsetToBottomEdge = max(0.0, self.webView.scrollView.contentSize.height - currentBounds.maxY)
var bottomInset = insets.bottom
if offsetToBottomEdge < 128.0 {
bottomInset = fullInsets.bottom
}
let webViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - bottomInset))
var refresh = false
if self.webView.frame.width > 0 && webViewFrame.width != self.webView.frame.width {
refresh = true
@ -539,11 +571,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.webView.reloadInputViews()
}
self.webView.scrollView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: fullInsets.bottom, right: 0.0)
self.webView.customBottomInset = max(insets.bottom, safeInsets.bottom)
self.webView.customBottomInset = safeInsets.bottom * (1.0 - insets.bottom / fullInsets.bottom)
self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
self.webView.scrollView.horizontalScrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
// self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
// self.webView.scrollView.horizontalScrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: -insets.left, bottom: 0.0, right: -insets.right)
if let error = self.currentError {
let errorSize = self.errorView.update(
@ -681,7 +712,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
// })
// } else {
if let url = navigationAction.request.url?.absoluteString {
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url)) {
if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url)) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") {
decisionHandler(.cancel, preferences)
self.minimize()
self.openAppUrl(url)
@ -725,7 +756,12 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
self.currentError = nil
if let _ = self.currentError {
self.currentError = nil
if let (size, insets, fullInsets, safeInsets) = self.validLayout {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: safeInsets, transition: .immediate)
}
}
self.updateFontState(self.currentFontState, force: true)
}
@ -739,7 +775,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if [-1003, -1100].contains((error as NSError).code) {
if [-1003, -1100, 102].contains((error as NSError).code) {
self.currentError = error
} else {
self.currentError = nil
@ -756,7 +792,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.minimize()
self.openAppUrl(url)
} else {
self.open(url: url, new: true)
return self.open(url: url, configuration: configuration, new: true)
}
}
}
@ -764,7 +800,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
}
func webViewDidClose(_ webView: WKWebView) {
self.close()
Queue.mainQueue().after(0.5, {
self.close()
})
}
@available(iOS 15.0, *)
@ -895,17 +933,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.present(alertController, nil)
}
private func open(url: String, new: Bool) {
@discardableResult private func open(url: String, configuration: WKWebViewConfiguration? = nil, new: Bool) -> WKWebView? {
let subject: BrowserScreen.Subject = .webPage(url: url)
if new, let navigationController = self.getNavigationController() {
navigationController._keepModalDismissProgress = true
self.minimize()
let controller = BrowserScreen(context: self.context, subject: subject, openPreviousOnClose: true)
let controller = BrowserScreen(context: self.context, subject: subject, preferredConfiguration: configuration, openPreviousOnClose: true)
navigationController._keepModalDismissProgress = true
navigationController.pushViewController(controller)
return (controller.node.content.last as? BrowserWebContent)?.webView
} else {
self.pushContent(subject)
}
return nil
}
private func share(url: String) {

View file

@ -108,3 +108,47 @@ func getPrimaryUrl(message: Message) -> String? {
}
return primaryUrl
}
private let asciiChars = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!)
func getDisplayUrl(_ url: String, hostOnly: Bool = false, trim: Bool = true) -> String {
if hostOnly {
var title = url
if let parsedUrl = URL(string: url) {
title = parsedUrl.host ?? url
if title.hasPrefix("www.") {
title.removeSubrange(title.startIndex ..< title.index(title.startIndex, offsetBy: 4))
}
if let decoded = title.idnaDecoded, title != decoded {
if decoded.lowercased().rangeOfCharacter(from: asciiChars) == nil {
title = decoded
}
}
}
return title
} else {
var address = url
if let components = URLComponents(string: address) {
if #available(iOS 16.0, *), let encodedHost = components.encodedHost {
if let decodedHost = components.host, encodedHost != decodedHost {
if decodedHost.lowercased().rangeOfCharacter(from: asciiChars) == nil {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
} else if let encodedHost = components.host {
if let decodedHost = components.host?.idnaDecoded, encodedHost != decodedHost {
if decodedHost.lowercased().rangeOfCharacter(from: asciiChars) == nil {
address = address.replacingOccurrences(of: encodedHost, with: decodedHost)
}
}
}
}
if trim {
address = address.replacingOccurrences(of: "https://www.", with: "")
address = address.replacingOccurrences(of: "https://", with: "")
address = address.replacingOccurrences(of: "tonsite://", with: "")
address = address.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
}
return address
}
}

View file

@ -58,6 +58,7 @@ swift_library(
"//submodules/Display:Display",
"//submodules/ImageBlur:ImageBlur",
"//submodules/TelegramCore:TelegramCore",
"//submodules/Utils/DeviceModel",
],
visibility = [
"//visibility:public",

View file

@ -4,6 +4,7 @@ import SwiftSignalKit
import AVFoundation
import CoreImage
import TelegramCore
import DeviceModel
final class CameraSession {
private let singleSession: AVCaptureSession?

View file

@ -1,4 +1,5 @@
import Foundation
import DeviceModel
public extension Camera {
enum Metrics {
@ -56,370 +57,3 @@ public extension Camera {
}
}
}
enum DeviceModel: CaseIterable, Equatable {
static var allCases: [DeviceModel] {
return [
.iPodTouch1,
.iPodTouch2,
.iPodTouch3,
.iPodTouch4,
.iPodTouch5,
.iPodTouch6,
.iPodTouch7,
.iPhone,
.iPhone3G,
.iPhone3GS,
.iPhone4,
.iPhone4S,
.iPhone5,
.iPhone5C,
.iPhone5S,
.iPhone6,
.iPhone6Plus,
.iPhone6S,
.iPhone6SPlus,
.iPhoneSE,
.iPhone7,
.iPhone7Plus,
.iPhone8,
.iPhone8Plus,
.iPhoneX,
.iPhoneXS,
.iPhoneXR,
.iPhone11,
.iPhone11Pro,
.iPhone11ProMax,
.iPhone12,
.iPhone12Mini,
.iPhone12Pro,
.iPhone12ProMax,
.iPhone13,
.iPhone13Mini,
.iPhone13Pro,
.iPhone13ProMax,
.iPhone14,
.iPhone14Plus,
.iPhone14Pro,
.iPhone14ProMax,
.iPhone15,
.iPhone15Plus,
.iPhone15Pro,
.iPhone15ProMax
]
}
case iPodTouch1
case iPodTouch2
case iPodTouch3
case iPodTouch4
case iPodTouch5
case iPodTouch6
case iPodTouch7
case iPhone
case iPhone3G
case iPhone3GS
case iPhone4
case iPhone4S
case iPhone5
case iPhone5C
case iPhone5S
case iPhone6
case iPhone6Plus
case iPhone6S
case iPhone6SPlus
case iPhoneSE
case iPhone7
case iPhone7Plus
case iPhone8
case iPhone8Plus
case iPhoneX
case iPhoneXS
case iPhoneXSMax
case iPhoneXR
case iPhone11
case iPhone11Pro
case iPhone11ProMax
case iPhoneSE2ndGen
case iPhone12
case iPhone12Mini
case iPhone12Pro
case iPhone12ProMax
case iPhone13
case iPhone13Mini
case iPhone13Pro
case iPhone13ProMax
case iPhoneSE3rdGen
case iPhone14
case iPhone14Plus
case iPhone14Pro
case iPhone14ProMax
case iPhone15
case iPhone15Plus
case iPhone15Pro
case iPhone15ProMax
case unknown(String)
var modelId: [String] {
switch self {
case .iPodTouch1:
return ["iPod1,1"]
case .iPodTouch2:
return ["iPod2,1"]
case .iPodTouch3:
return ["iPod3,1"]
case .iPodTouch4:
return ["iPod4,1"]
case .iPodTouch5:
return ["iPod5,1"]
case .iPodTouch6:
return ["iPod7,1"]
case .iPodTouch7:
return ["iPod9,1"]
case .iPhone:
return ["iPhone1,1"]
case .iPhone3G:
return ["iPhone1,2"]
case .iPhone3GS:
return ["iPhone2,1"]
case .iPhone4:
return ["iPhone3,1", "iPhone3,2", "iPhone3,3"]
case .iPhone4S:
return ["iPhone4,1", "iPhone4,2", "iPhone4,3"]
case .iPhone5:
return ["iPhone5,1", "iPhone5,2"]
case .iPhone5C:
return ["iPhone5,3", "iPhone5,4"]
case .iPhone5S:
return ["iPhone6,1", "iPhone6,2"]
case .iPhone6:
return ["iPhone7,2"]
case .iPhone6Plus:
return ["iPhone7,1"]
case .iPhone6S:
return ["iPhone8,1"]
case .iPhone6SPlus:
return ["iPhone8,2"]
case .iPhoneSE:
return ["iPhone8,4"]
case .iPhone7:
return ["iPhone9,1", "iPhone9,3"]
case .iPhone7Plus:
return ["iPhone9,2", "iPhone9,4"]
case .iPhone8:
return ["iPhone10,1", "iPhone10,4"]
case .iPhone8Plus:
return ["iPhone10,2", "iPhone10,5"]
case .iPhoneX:
return ["iPhone10,3", "iPhone10,6"]
case .iPhoneXS:
return ["iPhone11,2"]
case .iPhoneXSMax:
return ["iPhone11,4", "iPhone11,6"]
case .iPhoneXR:
return ["iPhone11,8"]
case .iPhone11:
return ["iPhone12,1"]
case .iPhone11Pro:
return ["iPhone12,3"]
case .iPhone11ProMax:
return ["iPhone12,5"]
case .iPhoneSE2ndGen:
return ["iPhone12,8"]
case .iPhone12:
return ["iPhone13,2"]
case .iPhone12Mini:
return ["iPhone13,1"]
case .iPhone12Pro:
return ["iPhone13,3"]
case .iPhone12ProMax:
return ["iPhone13,4"]
case .iPhone13:
return ["iPhone14,5"]
case .iPhone13Mini:
return ["iPhone14,4"]
case .iPhone13Pro:
return ["iPhone14,2"]
case .iPhone13ProMax:
return ["iPhone14,3"]
case .iPhoneSE3rdGen:
return ["iPhone14,6"]
case .iPhone14:
return ["iPhone14,7"]
case .iPhone14Plus:
return ["iPhone14,8"]
case .iPhone14Pro:
return ["iPhone15,2"]
case .iPhone14ProMax:
return ["iPhone15,3"]
case .iPhone15:
return ["iPhone15,4"]
case .iPhone15Plus:
return ["iPhone15,5"]
case .iPhone15Pro:
return ["iPhone16,1"]
case .iPhone15ProMax:
return ["iPhone16,2"]
case let .unknown(modelId):
return [modelId]
}
}
var modelName: String {
switch self {
case .iPodTouch1:
return "iPod touch 1G"
case .iPodTouch2:
return "iPod touch 2G"
case .iPodTouch3:
return "iPod touch 3G"
case .iPodTouch4:
return "iPod touch 4G"
case .iPodTouch5:
return "iPod touch 5G"
case .iPodTouch6:
return "iPod touch 6G"
case .iPodTouch7:
return "iPod touch 7G"
case .iPhone:
return "iPhone"
case .iPhone3G:
return "iPhone 3G"
case .iPhone3GS:
return "iPhone 3GS"
case .iPhone4:
return "iPhone 4"
case .iPhone4S:
return "iPhone 4S"
case .iPhone5:
return "iPhone 5"
case .iPhone5C:
return "iPhone 5C"
case .iPhone5S:
return "iPhone 5S"
case .iPhone6:
return "iPhone 6"
case .iPhone6Plus:
return "iPhone 6 Plus"
case .iPhone6S:
return "iPhone 6S"
case .iPhone6SPlus:
return "iPhone 6S Plus"
case .iPhoneSE:
return "iPhone SE"
case .iPhone7:
return "iPhone 7"
case .iPhone7Plus:
return "iPhone 7 Plus"
case .iPhone8:
return "iPhone 8"
case .iPhone8Plus:
return "iPhone 8 Plus"
case .iPhoneX:
return "iPhone X"
case .iPhoneXS:
return "iPhone XS"
case .iPhoneXSMax:
return "iPhone XS Max"
case .iPhoneXR:
return "iPhone XR"
case .iPhone11:
return "iPhone 11"
case .iPhone11Pro:
return "iPhone 11 Pro"
case .iPhone11ProMax:
return "iPhone 11 Pro Max"
case .iPhoneSE2ndGen:
return "iPhone SE (2nd gen)"
case .iPhone12:
return "iPhone 12"
case .iPhone12Mini:
return "iPhone 12 mini"
case .iPhone12Pro:
return "iPhone 12 Pro"
case .iPhone12ProMax:
return "iPhone 12 Pro Max"
case .iPhone13:
return "iPhone 13"
case .iPhone13Mini:
return "iPhone 13 mini"
case .iPhone13Pro:
return "iPhone 13 Pro"
case .iPhone13ProMax:
return "iPhone 13 Pro Max"
case .iPhoneSE3rdGen:
return "iPhone SE (3rd gen)"
case .iPhone14:
return "iPhone 14"
case .iPhone14Plus:
return "iPhone 14 Plus"
case .iPhone14Pro:
return "iPhone 14 Pro"
case .iPhone14ProMax:
return "iPhone 14 Pro Max"
case .iPhone15:
return "iPhone 15"
case .iPhone15Plus:
return "iPhone 15 Plus"
case .iPhone15Pro:
return "iPhone 15 Pro"
case .iPhone15ProMax:
return "iPhone 15 Pro Max"
case let .unknown(modelId):
if modelId.hasPrefix("iPhone") {
return "Unknown iPhone"
} else if modelId.hasPrefix("iPod") {
return "Unknown iPod"
} else if modelId.hasPrefix("iPad") {
return "Unknown iPad"
} else {
return "Unknown Device"
}
}
}
var isIpad: Bool {
return self.modelId.first?.hasPrefix("iPad") ?? false
}
static let current = DeviceModel()
private init() {
var systemInfo = utsname()
uname(&systemInfo)
let modelCode = withUnsafePointer(to: &systemInfo.machine) {
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
ptr in String.init(validatingUTF8: ptr)
}
}
var result: DeviceModel?
if let modelCode {
for model in DeviceModel.allCases {
if model.modelId.contains(modelCode) {
result = model
break
}
}
}
if let result {
self = result
} else {
self = .unknown(modelCode ?? "")
}
}
}

View file

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

View file

@ -433,7 +433,7 @@ public func chatTextLinkEditController(sharedContext: SharedAccountContext, upda
return
}
let updatedLink = explicitUrl(contentNode.link)
if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false]) {
if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) {
dismissImpl?(true)
apply(updatedLink)
} else if allowEmpty && contentNode.link.isEmpty {

View file

@ -6,12 +6,15 @@ public protocol MinimizedContainer: ASDisplayNode {
var controllers: [MinimizableController] { get }
var isExpanded: Bool { get }
var willMaximize: (() -> Void)? { get set }
var willMaximize: ((MinimizedContainer) -> Void)? { get set }
var willDismiss: ((MinimizedContainer) -> Void)? { get set }
var didDismiss: ((MinimizedContainer) -> Void)? { get set }
var statusBarStyle: StatusBarStyle { get }
var statusBarStyleUpdated: (() -> Void)? { get set }
func addController(_ viewController: MinimizableController, topEdgeOffset: CGFloat?, beforeMaximize: @escaping (NavigationController, @escaping () -> Void) -> Void, transition: ContainedViewLayoutTransition)
func removeController(_ viewController: MinimizableController)
func maximizeController(_ viewController: MinimizableController, animated: Bool, completion: @escaping (Bool) -> Void)
func collapse()
func dismissAll(completion: @escaping () -> Void)

View file

@ -153,13 +153,23 @@ open class NavigationController: UINavigationController, ContainableController,
open var minimizedContainer: MinimizedContainer? {
didSet {
self.minimizedContainer?.navigationController = self
self.minimizedContainer?.willMaximize = { [weak self] in
self.minimizedContainer?.willMaximize = { [weak self] _ in
guard let self else {
return
}
self.isMaximizing = true
self.updateContainersNonReentrant(transition: .animated(duration: 0.4, curve: .spring))
}
self.minimizedContainer?.willDismiss = { [weak self] _ in
guard let self else {
return
}
self.minimizedContainer = nil
self.updateContainersNonReentrant(transition: .animated(duration: 0.4, curve: .spring))
}
self.minimizedContainer?.didDismiss = { minimizedContainer in
minimizedContainer.removeFromSupernode()
}
self.minimizedContainer?.statusBarStyleUpdated = { [weak self] in
guard let self else {
return

View file

@ -209,7 +209,7 @@ public final class DrawingLinkEntityView: DrawingEntityView, UITextViewDelegate
if !self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
string = self.linkEntity.name.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
} else {
string = self.linkEntity.url.uppercased()
string = self.linkEntity.url.uppercased().replacingOccurrences(of: "http://", with: "").replacingOccurrences(of: "https://", with: "")
}
let text = NSMutableAttributedString(string: string)
let range = NSMakeRange(0, text.length)

View file

@ -624,6 +624,15 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
}
func getRenderSubEntities() -> [DrawingEntity] {
var explicitlyStaticStickers = Set<Int64>()
if let customEmojiContainerView = self.customEmojiContainerView {
for (key, view) in customEmojiContainerView.emojiLayers {
if let view = view as? EmojiTextAttachmentView, let numFrames = view.contentLayer.numFrames, numFrames == 1 {
explicitlyStaticStickers.insert(key.id)
}
}
}
let textSize = self.textView.bounds.size
let textPosition = self.textEntity.position
let scale = self.textEntity.scale
@ -638,6 +647,9 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate
let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0)
let entity = DrawingStickerEntity(content: .file(.standalone(media: file), .sticker))
if explicitlyStaticStickers.contains(file.fileId.id) {
entity.isExplicitlyStatic = true
}
entity.referenceDrawingSize = CGSize(width: itemSize * 4.0, height: itemSize * 4.0)
entity.scale = scale
entity.position = textPosition.offsetBy(

View file

@ -8,16 +8,6 @@ import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
public struct InstantPageSourceLocation {
public var userLocation: MediaResourceUserLocation
public var peerType: MediaAutoDownloadPeerType
public init(userLocation: MediaResourceUserLocation, peerType: MediaAutoDownloadPeerType) {
self.userLocation = userLocation
self.peerType = peerType
}
}
public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? {
for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {

View file

@ -7,7 +7,7 @@ import SwiftSignalKit
import AccountContext
import TelegramUIPreferences
final class InstantPageReferenceController: ViewController {
public final class InstantPageReferenceController: ViewController {
private var controllerNode: InstantPageReferenceControllerNode {
return self.displayNode as! InstantPageReferenceControllerNode
}
@ -23,7 +23,7 @@ final class InstantPageReferenceController: ViewController {
private let openUrlIn: (InstantPageUrlItem) -> Void
private let present: (ViewController, Any?) -> Void
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
public init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.sourceLocation = sourceLocation
self.theme = theme

View file

@ -38,10 +38,10 @@ struct InstantPageTextImageItem {
let id: EngineMedia.Id
}
struct InstantPageTextAnchorItem {
let name: String
let anchorText: NSAttributedString?
let empty: Bool
public struct InstantPageTextAnchorItem {
public let name: String
public let anchorText: NSAttributedString?
public let empty: Bool
}
public struct InstantPageTextRangeRectEdge: Equatable {
@ -63,7 +63,7 @@ public final class InstantPageTextLine {
let strikethroughItems: [InstantPageTextStrikethroughItem]
let markedItems: [InstantPageTextMarkedItem]
let imageItems: [InstantPageTextImageItem]
let anchorItems: [InstantPageTextAnchorItem]
public let anchorItems: [InstantPageTextAnchorItem]
let isRTL: Bool
init(line: CTLine, range: NSRange, frame: CGRect, strikethroughItems: [InstantPageTextStrikethroughItem], markedItems: [InstantPageTextMarkedItem], imageItems: [InstantPageTextImageItem], anchorItems: [InstantPageTextAnchorItem], isRTL: Bool) {

View file

@ -477,16 +477,18 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
}
func lcm(_ x: Int64, _ y: Int64) -> Int64 {
let x = max(x, 1)
let y = max(y, 1)
return x / gcd(x, y) * y
}
return combineLatest(durations)
|> map { durations in
var result: Double
let minDuration: Double = 3.0
if durations.count > 1 {
let reduced = durations.reduce(1.0) { lhs, rhs -> Double in
return Double(lcm(Int64(lhs * 10.0), Int64(rhs * 10.0)))
return Double(lcm(Int64(lhs * 100.0), Int64(rhs * 100.0)))
}
result = min(6.0, Double(reduced) / 10.0)
} else if let duration = durations.first {

View file

@ -1104,7 +1104,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
self.hideButtonNode.isHidden = confirmation
case .email:
self.inputNode.textField.keyboardType = .emailAddress
self.inputNode.textField.returnKeyType = .done
self.inputNode.textField.returnKeyType = .next
self.hideButtonNode.isHidden = true
if #available(iOS 12.0, *) {
@ -1134,7 +1134,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega
}
case .hint:
self.inputNode.textField.keyboardType = .asciiCapable
self.inputNode.textField.returnKeyType = .done
self.inputNode.textField.returnKeyType = .next
self.hideButtonNode.isHidden = true
self.inputNode.textField.autocorrectionType = .no

View file

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

View file

@ -16,6 +16,7 @@ import WebKit
import LinkPresentation
import CoreServices
import PersistentStringHash
import UrlHandling
private final class WebBrowserSettingsControllerArguments {
let context: AccountContext
@ -453,21 +454,6 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
return controller
}
private func cleanDomain(url: String) -> (domain: String, fullUrl: String) {
if let parsedUrl = URL(string: url) {
let host: String?
let scheme = parsedUrl.scheme ?? "https"
if #available(iOS 16.0, *) {
host = parsedUrl.host(percentEncoded: true)?.lowercased()
} else {
host = parsedUrl.host?.lowercased()
}
return (host ?? url, "\(scheme)://\(host ?? "")")
} else {
return (url, url)
}
}
private func fetchDomainExceptionInfo(context: AccountContext, url: String) -> Signal<WebBrowserException, NoError> {
let (domain, domainUrl) = cleanDomain(url: url)
if #available(iOS 13.0, *), let url = URL(string: domainUrl) {

View file

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

View file

@ -320,7 +320,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openUrl(url.url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController)
if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) {
navigationController.pushViewController(controller)
}
}
}, openWallpaper: { [weak self] message in
if let strongSelf = self{
@ -1223,8 +1225,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}
case .chatFolder:
break
case let .instantView(webpage, anchor):
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel), anchor: anchor))
case let .instantView(webPage, anchor):
let browserController = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel))
strongSelf.pushController(browserController)
case let .join(link):
strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in
if let strongSelf = self {
@ -1257,6 +1260,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .premiumOffer:
break
case .starsTopup:
break
case let .joinVoiceChat(peerId, invite):
strongSelf.presentController(VoiceChatJoinScreen(context: strongSelf.context, peerId: peerId, invite: invite, join: { call in
}), .window(.root), nil)

View file

@ -776,7 +776,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
}
public final class EmojiTextAttachmentView: UIView {
private let contentLayer: InlineStickerItemLayer
public let contentLayer: InlineStickerItemLayer
public var isActive: Bool = true {
didSet {
@ -826,7 +826,7 @@ public final class EmojiTextAttachmentView: UIView {
public final class CustomEmojiContainerView: UIView {
private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView?
private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:]
public private(set) var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:]
public init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) {
self.emojiViewProvider = emojiViewProvider

View file

@ -1,5 +1,6 @@
import Foundation
import TelegramCore
import UrlEscaping
public func decodeCodableDrawingEntities(data: Data) -> [CodableDrawingEntity] {
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
@ -183,13 +184,9 @@ public enum CodableDrawingEntity: Equatable {
return nil
}
case let .link(entity):
var url = entity.url
if !url.hasPrefix("http://") && !url.hasPrefix("https://") {
url = "https://\(url)"
}
return .link(
coordinates: coordinates,
url: url
url: explicitUrl(entity.url)
)
case let .weather(entity):
let color: UInt32

View file

@ -88,11 +88,15 @@ public final class DrawingTextEntity: DrawingEntity, Codable {
return true
}
var isAnimated = false
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
isAnimated = true
if let renderSubEntities = self.renderSubEntities {
for entity in renderSubEntities {
if entity.isAnimated {
isAnimated = true
break
}
}
})
}
return isAnimated
}

View file

@ -466,14 +466,21 @@ private final class CreateLinkSheetComponent: CombinedComponent {
let text = !self.name.isEmpty ? self.name : self.link
var effectiveMedia: TelegramMediaWebpage?
if let webpage = self.webpage, case .Loaded = webpage.content, !self.dismissed {
var webpageHasLargeMedia = false
if let webpage = self.webpage, case let .Loaded(content) = webpage.content, !self.dismissed {
effectiveMedia = webpage
if let isMediaLargeByDefault = content.isMediaLargeByDefault, isMediaLargeByDefault {
webpageHasLargeMedia = true
} else {
webpageHasLargeMedia = true
}
}
var attributes: [MessageAttribute] = []
attributes.append(TextEntitiesMessageAttribute(entities: [.init(range: 0 ..< (text as NSString).length, type: .Url)]))
if !self.dismissed {
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !self.positionBelowText, forceLargeMedia: self.largeMedia, isManuallyAdded: false, isSafe: true))
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !self.positionBelowText, forceLargeMedia: self.largeMedia ?? webpageHasLargeMedia, isManuallyAdded: false, isSafe: true))
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))

View file

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

View file

@ -10,6 +10,7 @@ import UIKitRuntimeUtils
private let minimizedNavigationHeight: CGFloat = 44.0
private let minimizedTopMargin: CGFloat = 3.0
private let maximizeLastStandingController = false
final class ScrollViewImpl: UIScrollView {
var shouldPassthrough: () -> Bool = { return false }
@ -112,7 +113,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
self.snapshotContainerView.isUserInteractionEnabled = false
super.init()
self.clipsToBounds = true
self.cornerRadius = 10.0
applySmoothRoundedCorners(self.layer)
@ -308,7 +309,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
private var presentationDataDisposable: Disposable?
public private(set) var isExpanded: Bool = false
public var willMaximize: (() -> Void)?
public var willMaximize: ((MinimizedContainer) -> Void)?
public var willDismiss: ((MinimizedContainer) -> Void)?
public var didDismiss: ((MinimizedContainer) -> Void)?
public private(set) var statusBarStyle: StatusBarStyle = .White
public var statusBarStyleUpdated: (() -> Void)?
@ -500,10 +503,13 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
self.currentTransition = .dismiss(itemId: itemId)
self.items.removeAll(where: { $0.id == itemId })
if self.items.count == 1 {
if self.items.count == 1, maximizeLastStandingController {
self.isExpanded = false
self.willMaximize?()
self.willMaximize?(self)
needsLayout = false
} else if self.items.count == 0 {
self.willDismiss?(self)
self.isExpanded = false
}
}
if let item = self.items.first(where: { $0.id == itemId }), !item.controller.shouldDismissImmediately() {
@ -558,6 +564,15 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
self.requestUpdate(transition: transition)
}
public func removeController(_ viewController: MinimizableController) {
guard let item = self.items.first(where: { $0.controller === viewController }) else {
return
}
self.items.removeAll(where: { $0.id == item.id })
self.requestUpdate(transition: .animated(duration: 0.25, curve: .easeInOut))
}
private enum Transition: Equatable {
case minimize(itemId: AnyHashable)
case maximize(itemId: AnyHashable)
@ -759,7 +774,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
if let currentTransition = self.currentTransition {
if currentTransition.matches(item: item) {
continue
} else if case .dismiss = currentTransition, self.items.count == 1 {
} else if case .dismiss = currentTransition, self.items.count == 1 && maximizeLastStandingController {
continue
}
}
@ -789,10 +804,13 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
self.currentTransition = .dismiss(itemId: item.id)
self.items.removeAll(where: { $0.id == item.id })
if self.items.count == 1 {
if self.items.count == 1, maximizeLastStandingController {
self.isExpanded = false
self.willMaximize?()
self.willMaximize?(self)
needsLayout = false
} else if self.items.count == 0 {
self.isExpanded = false
self.willDismiss?(self)
}
if needsLayout {
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
@ -1097,7 +1115,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
guard let dismissedItemNode = self.itemNodes[itemId] else {
return
}
if self.items.count == 1 {
if self.items.count == 1, maximizeLastStandingController {
if let itemNode = self.itemNodes.first(where: { $0.0 != itemId })?.value, let navigationController = self.navigationController {
itemNode.item.beforeMaximize(navigationController, { [weak self] in
guard let self else {
@ -1137,6 +1155,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
}
transition.updatePosition(node: dismissedItemNode, position: CGPoint(x: -layout.size.width, y: dismissedItemNode.position.y))
} else {
let isLast = self.items.isEmpty
transition.updatePosition(node: dismissedItemNode, position: CGPoint(x: -layout.size.width, y: dismissedItemNode.position.y), completion: { _ in
self.isApplyingTransition = false
if self.currentTransition == currentTransition {
@ -1146,7 +1165,15 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
self.itemNodes[itemId] = nil
dismissedItemNode.removeFromSupernode()
if isLast {
self.didDismiss?(self)
}
})
if isLast {
let dismissOffset = collapsedHeight(layout: layout)
transition.updatePosition(layer: self.bottomEdgeView.layer, position: self.bottomEdgeView.layer.position.offsetBy(dx: 0.0, dy: dismissOffset))
}
}
case .dismissAll:
let dismissOffset = collapsedHeight(layout: layout)

View file

@ -73,7 +73,7 @@ final class MinimizedHeaderNode: ASDisplayNode {
guard let self else {
return
}
let titles = titles.compactMap { $0 }
let titles = titles.compactMap { $0 }.filter { !$0.isEmpty }
if titles.count == 1, let title = titles.first {
self.title = title
} else if let title = titles.last {

View file

@ -113,7 +113,10 @@ func interitemSpacing(itemCount: Int, boundingSize: CGSize, insets: UIEdgeInsets
func frameForIndex(index: Int, size: CGSize, insets: UIEdgeInsets, itemCount: Int, boundingSize: CGSize) -> CGRect {
let spacing = interitemSpacing(itemCount: itemCount, boundingSize: boundingSize, insets: insets)
let y = additionalInsetTop + insets.top + spacing * CGFloat(index)
var y = additionalInsetTop + insets.top + spacing * CGFloat(index)
if itemCount == 1 {
y += 72.0
}
let origin = CGPoint(x: insets.left, y: y)
return CGRect(origin: origin, size: CGSize(width: size.width - insets.left - insets.right, height: size.height))

View file

@ -17,6 +17,7 @@ private var nextRenderTargetId: Int64 = 1
open class MultiAnimationRenderTarget: SimpleLayer {
public let id: Int64
public var numFrames: Int?
let deinitCallbacks = Bag<() -> Void>()
let updateStateCallbacks = Bag<() -> Void>()
@ -545,6 +546,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
}
target.contents = loadedFrame.image.cgImage
target.numFrames = item.numFrames
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
blurredRepresentationTarget.contents = loadedFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
@ -580,6 +582,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
completion(false, true)
return
}
target.numFrames = item.numFrames
if let loadedFrame = loadedFrame {
if let cgImage = loadedFrame.image.cgImage {
if hadIntermediateUpdate {

View file

@ -3351,7 +3351,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
if let foundGalleryMessage = foundGalleryMessage {
strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: foundGalleryMessage, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController)
if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: foundGalleryMessage, sourcePeerType: associatedData?.automaticDownloadPeerType) {
navigationController.pushViewController(controller)
}
}
}, openWallpaper: { _ in
}, openTheme: { _ in

View file

@ -1690,8 +1690,12 @@ final class ShareWithPeersScreenComponent: Component {
title: item.title,
image: item.image,
hasNext: false,
action: {
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.editCover()
self.saveAndDismiss()
}
)),
environment: {},
@ -1993,6 +1997,36 @@ final class ShareWithPeersScreenComponent: Component {
private var currentHasChannels: Bool?
private var currentHasCover: Bool?
func saveAndDismiss() {
guard let component = self.component, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
return
}
let base: EngineStoryPrivacy.Base
if self.selectedCategories.contains(.everyone) {
base = .everyone
} else if self.selectedCategories.contains(.closeFriends) {
base = .closeFriends
} else if self.selectedCategories.contains(.contacts) {
base = .contacts
} else if self.selectedCategories.contains(.selectedContacts) {
base = .nobody
} else {
base = .nobody
}
component.completion(
self.sendAsPeerId,
EngineStoryPrivacy(
base: base,
additionallyIncludePeers: self.selectedPeers
),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? [],
false
)
controller.requestDismiss()
}
func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
guard !self.isDismissed else {
return availableSize
@ -2475,33 +2509,10 @@ final class ShareWithPeersScreenComponent: Component {
component: AnyComponent(Button(
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)),
action: { [weak self] in
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
guard let self else {
return
}
let base: EngineStoryPrivacy.Base
if self.selectedCategories.contains(.everyone) {
base = .everyone
} else if self.selectedCategories.contains(.closeFriends) {
base = .closeFriends
} else if self.selectedCategories.contains(.contacts) {
base = .contacts
} else if self.selectedCategories.contains(.selectedContacts) {
base = .nobody
} else {
base = .nobody
}
component.completion(
self.sendAsPeerId,
EngineStoryPrivacy(
base: base,
additionallyIncludePeers: self.selectedPeers
),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? [],
false
)
controller.requestDismiss()
self.saveAndDismiss()
}
).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
environment: {},

View file

@ -208,7 +208,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
let textString: String
switch context.component.purpose {
case .generic:
case let .generic(requiredStars):
let _ = requiredStars
textString = strings.Stars_Purchase_GetStarsInfo
case .gift:
textString = strings.Stars_Purchase_GiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
@ -299,8 +300,13 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
if let products = state.products, let balance = context.component.balance {
var minimumCount: Int64?
if let requiredStars = context.component.purpose.requiredStars {
minimumCount = requiredStars - balance
if case .generic = context.component.purpose {
minimumCount = requiredStars
} else {
minimumCount = requiredStars - balance
}
}
for product in products {
if let minimumCount, minimumCount > product.count && !(items.isEmpty && product.id == products.last?.id) {
continue
@ -810,8 +816,12 @@ private final class StarsPurchaseScreenComponent: CombinedComponent {
let titleText: String
switch context.component.purpose {
case .generic:
titleText = strings.Stars_Purchase_GetStars
case let .generic(requiredStars):
if let requiredStars {
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
} else {
titleText = strings.Stars_Purchase_GetStars
}
case .gift:
titleText = strings.Stars_Purchase_GiftStars
case let .transfer(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars):
@ -1216,6 +1226,8 @@ private extension StarsPurchasePurpose {
var requiredStars: Int64? {
switch self {
case let .generic(requiredStars):
return requiredStars
case let .transfer(_, requiredStars):
return requiredStars
case let .subscription(_, requiredStars, _):

View file

@ -806,7 +806,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
guard let self else {
return
}
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, completion: { [weak self] stars in
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic(requiredStars: nil), completion: { [weak self] stars in
guard let self else {
return
}

View file

@ -484,7 +484,7 @@ private final class SheetContent: CombinedComponent {
} else if let peerId = state?.botPeer?.id {
purpose = .transfer(peerId: peerId, requiredStars: invoice.totalAmount)
} else {
purpose = .generic
purpose = .generic(requiredStars: nil)
}
let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen(
context: accountContext,

View file

@ -6118,7 +6118,7 @@ public final class StoryItemSetContainerComponent: Component {
self.openStoryEditing()
})))
if case .file = component.slice.item.storyItem.media {
if case .file = component.slice.item.storyItem.media, component.slice.item.storyItem.isPinned {
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_EditCover, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/EditCover"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
@ -6316,7 +6316,7 @@ public final class StoryItemSetContainerComponent: Component {
self.openStoryEditing()
})))
if case .file = component.slice.item.storyItem.media {
if case .file = component.slice.item.storyItem.media, component.slice.item.storyItem.isPinned {
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_EditCover, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/EditCover"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in

View file

@ -402,156 +402,160 @@ public extension ChatControllerImpl {
}
}
func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) {
func presentBotApp(botApp: BotApp?, botPeer: EnginePeer, payload: String?, compact: Bool, concealed: Bool = false, commit: @escaping () -> Void = {}) {
guard let peerId = self.chatLocation.peerId else {
return
}
self.attachmentController?.dismiss(animated: true, completion: nil)
let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in
guard let strongSelf = self else {
return
}
commit()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
let updateProgress = { [weak self] in
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}
let botAddress = botPeer.addressName ?? ""
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite)
|> afterDisposed {
updateProgress()
})
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
if let botApp {
let openBotApp: (Bool, Bool) -> Void = { [weak self] allowWrite, justInstalled in
guard let strongSelf = self else {
return
}
let context = strongSelf.context
let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize))
var presentImpl: ((ViewController, Any?) -> Void)?
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in
presentImpl?(c, a)
}, commit: commit)
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
}, completion: { [weak self] in
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
}, getNavigationController: { [weak self] in
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
commit()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
controller.navigationPresentation = .flatModal
strongSelf.currentWebAppController = controller
strongSelf.push(controller)
presentImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
if justInstalled {
let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil)
controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current)
}
}, error: { [weak self] error in
if let strongSelf = self {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}))
}
let _ = combineLatest(
queue: Queue.mainQueue(),
ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id),
self.context.engine.messages.attachMenuBots(),
self.context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true)
|> map(Optional.init)
|> `catch` { _ -> Signal<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 updateProgress = { [weak self] in
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}
let botAddress = botPeer.addressName ?? ""
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), compact: compact, allowWrite: allowWrite)
|> afterDisposed {
updateProgress()
})
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
guard let strongSelf = self else {
return
}
let context = strongSelf.context
let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize))
var presentImpl: ((ViewController, Any?) -> Void)?
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: self, url: url, concealed: concealed, present: { c, a in
presentImpl?(c, a)
}, commit: commit)
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
ChatControllerImpl.botRequestSwitchInline(context: context, controller: self, peerId: peerId, botAddress: botAddress, query: query, chatTypes: chatTypes, completion: completion)
}, completion: { [weak self] in
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
}, getNavigationController: { [weak self] in
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
})
controller.navigationPresentation = .flatModal
strongSelf.currentWebAppController = controller
strongSelf.push(controller)
presentImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
if justInstalled {
let content: UndoOverlayContent = .succeed(text: strongSelf.presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil)
controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current)
}
}, error: { [weak self] error in
if let strongSelf = self {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
}))
}
let _ = combineLatest(
queue: Queue.mainQueue(),
ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id),
self.context.engine.messages.attachMenuBots(),
self.context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true)
|> map(Optional.init)
|> `catch` { _ -> Signal<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))
} else {
openBotApp(false, false)
}
} else {
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: self.updatedPresentationData, peer: botPeer, requestWriteAccess: botApp.flags.contains(.notActivated) && botApp.flags.contains(.requiresWriteAccess), completion: { allowWrite in
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
openBotApp(allowWrite, false)
}, showMore: { [weak self] in
if let self {
self.openResolved(result: .peer(botPeer._asPeer(), .info(nil)), sourceMessageId: nil)
}
})
self.present(controller, in: .window(.root))
openBotApp(false, false)
}
} else {
openBotApp(false, false)
}
})
})
} else {
self.context.sharedContext.openWebApp(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, peer: botPeer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false)
}
}
}

View file

@ -2532,8 +2532,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) {
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
strongSelf.chatDisplayNode.dismissInput()
strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController)
if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) {
navigationController.pushViewController(controller)
}
if case .overlay = strongSelf.presentationInterfaceState.mode {
strongSelf.chatDisplayNode.dismissAsOverlay()
}
@ -9211,10 +9212,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
if let strongSelf = self, let peer {
strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: {
dismissWebAppControllers()
if let botApp = botAppStart.botApp {
strongSelf.presentBotApp(botApp: botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: {
dismissWebAppControllers()
commit()
})
} else {
strongSelf.context.sharedContext.openWebApp(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false)
commit()
})
}
}
})
default:
@ -9256,7 +9262,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break
default:
progress?.set(.single(false))
self.context.sharedContext.openChatInstantPage(context: self.context, message: message, sourcePeerType: nil, navigationController: navigationController)
if let controller = self.context.sharedContext.makeInstantPageController(context: self.context, message: message, sourcePeerType: nil) {
navigationController.pushViewController(controller)
}
return
}
}

View file

@ -381,18 +381,16 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
return false
}
func openChatInstantPageImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) {
if let (webpage, anchor) = instantPageAndAnchor(message: message) {
let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel)
let pageController: ViewController
if context.sharedContext.immediateExperimentalUISettings.browserExperiment {
pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, anchor: anchor, sourceLocation: sourceLocation))
} else {
pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor)
}
navigationController.pushViewController(pageController)
func makeInstantPageControllerImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? {
guard let (webpage, anchor) = instantPageAndAnchor(message: message) else {
return nil
}
let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel)
return makeInstantPageControllerImpl(context: context, webPage: webpage, anchor: anchor, sourceLocation: sourceLocation)
}
func makeInstantPageControllerImpl(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController {
return BrowserScreen(context: context, subject: .instantPage(webPage: webPage, anchor: anchor, sourceLocation: sourceLocation))
}
func openChatWallpaperImpl(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {

View file

@ -248,15 +248,10 @@ func openResolvedUrlImpl(
}
})
present(controller, nil)
case let .instantView(webpage, anchor):
case let .instantView(webPage, anchor):
let sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel)
let pageController: ViewController
if context.sharedContext.immediateExperimentalUISettings.browserExperiment {
pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, anchor: anchor, sourceLocation: sourceLocation))
} else {
pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor)
}
navigationController?.pushViewController(pageController)
let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation)
navigationController?.pushViewController(browserController)
case let .join(link):
dismissInput()
@ -661,6 +656,14 @@ func openResolvedUrlImpl(
if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true)
}
case let .starsTopup(amount):
dismissInput()
if let starsContext = context.starsContext {
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .generic(requiredStars: amount), completion: { _ in })
if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true)
}
}
case let .joinVoiceChat(peerId, invite):
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in

View file

@ -918,6 +918,20 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
}
}
handleResolvedUrl(.premiumMultiGift(reference: reference))
} else if parsedUrl.host == "stars_topup" {
var amount: Int64?
if let components = URLComponents(string: "/?" + query) {
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "amount" {
amount = Int64(value)
}
}
}
}
}
handleResolvedUrl(.starsTopup(amount: amount))
} else if parsedUrl.host == "addlist" {
if let components = URLComponents(string: "/?" + query) {
var slug: String?
@ -1047,7 +1061,14 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
let _ = (settings
|> deliverOnMainQueue).startStandalone(next: { settings in
if let defaultWebBrowser = settings.defaultWebBrowser, defaultWebBrowser != "inApp" {
var isTonSite = false
if let host = parsedUrl.host, host.lowercased().hasSuffix(".ton") {
isTonSite = true
} else if let scheme = parsedUrl.scheme, scheme.lowercased().hasPrefix("tonsite") {
isTonSite = true
}
if let defaultWebBrowser = settings.defaultWebBrowser, defaultWebBrowser != "inApp" && !isTonSite {
let openInOptions = availableOpenInOptions(context: context, item: .url(url: url))
if let option = openInOptions.first(where: { $0.identifier == settings.defaultWebBrowser }) {
if case let .openUrl(openInUrl) = option.action() {
@ -1067,8 +1088,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
break
}
}
if settings.defaultWebBrowser == nil && !isExceptedDomain {
if (settings.defaultWebBrowser == nil && !isExceptedDomain) || isTonSite {
let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString))
navigationController?.pushViewController(controller)
} else {

View file

@ -1846,8 +1846,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
})
}
public func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) {
openChatInstantPageImpl(context: context, message: message, sourcePeerType: sourcePeerType, navigationController: navigationController)
public func makeInstantPageController(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? {
return makeInstantPageControllerImpl(context: context, message: message, sourcePeerType: sourcePeerType)
}
public func makeInstantPageController(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController {
return makeInstantPageControllerImpl(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation)
}
public func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {

View file

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

View file

@ -15,6 +15,7 @@ import JoinLinkPreviewUI
import PresentationDataUtils
import UrlWhitelist
import UndoUI
import BrowserUI
func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) {
let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in
@ -87,8 +88,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
case let .stickerPack(name, _):
let packReference: StickerPackReference = .name(name)
controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
case let .instantView(webpage, anchor):
(controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourceLocation: InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group), anchor: anchor))
case let .instantView(webPage, anchor):
let sourceLocation = InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group)
let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation)
(controller.navigationController as? NavigationController)?.pushViewController(browserController, animated: true)
case let .join(link):
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))

View file

@ -491,6 +491,16 @@ public enum MediaAutoDownloadPeerType {
case channel
}
public struct InstantPageSourceLocation {
public var userLocation: MediaResourceUserLocation
public var peerType: MediaAutoDownloadPeerType
public init(userLocation: MediaResourceUserLocation, peerType: MediaAutoDownloadPeerType) {
self.userLocation = userLocation
self.peerType = peerType
}
}
public func effectiveAutodownloadCategories(settings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) -> MediaAutoDownloadCategories {
let connection = settings.connectionSettings(for: networkType)
switch connection.preset {

View file

@ -22,7 +22,7 @@ public extension CharacterSet {
}()
}
public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": true, "https": true]) -> Bool {
public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": true, "https": true, "tonsite": true]) -> Bool {
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), let scheme = url.scheme?.lowercased(), let requiresTopLevelDomain = validSchemes[scheme], let host = url.host, (!requiresTopLevelDomain || host.contains(".")) && url.user == nil {
if requiresTopLevelDomain {
let components = host.components(separatedBy: ".")
@ -39,8 +39,12 @@ public func isValidUrl(_ url: String, validSchemes: [String: Bool] = ["http": tr
public func explicitUrl(_ url: String) -> String {
var url = url
if !url.hasPrefix("http") && !url.hasPrefix("https") && url.range(of: "://") == nil {
url = "https://\(url)"
if !url.lowercased().hasPrefix("http:") && !url.lowercased().hasPrefix("https:") && !url.lowercased().hasPrefix("tonsite:") && url.range(of: "://") == nil {
if let parsedUrl = URL(string: "http://\(url)"), parsedUrl.host?.hasSuffix(".ton") == true {
url = "tonsite://\(url)"
} else {
url = "https://\(url)"
}
}
return url
}

View file

@ -292,7 +292,20 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
}
}
return .startAttach(peerName, value, choose)
} else if queryItem.name == "story" {
} else if queryItem.name == "startapp" {
var compact = false
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "mode", value == "compact" {
compact = true
break
}
}
}
}
return .peer(.name(peerName), .appStart("", queryItem.value, compact))
} else if queryItem.name == "story" {
if let id = Int32(value) {
return .peer(.name(peerName), .story(id))
}
@ -321,6 +334,19 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
return .peer(.name(peerName), .boost)
} else if queryItem.name == "profile" {
return .peer(.name(peerName), .profile)
} else if queryItem.name == "startapp" {
var compact = false
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "mode", value == "compact" {
compact = true
break
}
}
}
}
return .peer(.name(peerName), .appStart("", nil, compact))
}
}
}
@ -720,18 +746,26 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
}
case let .appStart(name, payload, compact):
return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false)
|> map(Optional.init)
|> `catch` { _ -> Signal<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)))))
if name.isEmpty {
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
return .single(.result(.peer(peer._asPeer(), .withBotApp(ChatControllerInitialBotAppStart(botApp: nil, payload: payload, justInstalled: false, compact: compact)))))
} else {
return .single(.result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))))
}
})
} else {
return .single(.progress) |> then(context.engine.messages.getBotApp(botId: peer.id, shortName: name, cached: false)
|> map(Optional.init)
|> `catch` { _ -> Signal<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):
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id)
@ -1299,3 +1333,18 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal<Resol
}
}
}
public func cleanDomain(url: String) -> (domain: String, fullUrl: String) {
if let parsedUrl = URL(string: url) {
let host: String?
let scheme = parsedUrl.scheme ?? "https"
if #available(iOS 16.0, *) {
host = parsedUrl.host(percentEncoded: true)?.lowercased()
} else {
host = parsedUrl.host?.lowercased()
}
return (host ?? url, "\(scheme)://\(host ?? "")")
} else {
return (url, url)
}
}

View file

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "DeviceModel",
module_name = "DeviceModel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/LegacyComponents",
"//submodules/AccountContext",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,368 @@
import Foundation
public enum DeviceModel: CaseIterable, Equatable {
public static var allCases: [DeviceModel] {
return [
.iPodTouch1,
.iPodTouch2,
.iPodTouch3,
.iPodTouch4,
.iPodTouch5,
.iPodTouch6,
.iPodTouch7,
.iPhone,
.iPhone3G,
.iPhone3GS,
.iPhone4,
.iPhone4S,
.iPhone5,
.iPhone5C,
.iPhone5S,
.iPhone6,
.iPhone6Plus,
.iPhone6S,
.iPhone6SPlus,
.iPhoneSE,
.iPhone7,
.iPhone7Plus,
.iPhone8,
.iPhone8Plus,
.iPhoneX,
.iPhoneXS,
.iPhoneXR,
.iPhone11,
.iPhone11Pro,
.iPhone11ProMax,
.iPhone12,
.iPhone12Mini,
.iPhone12Pro,
.iPhone12ProMax,
.iPhone13,
.iPhone13Mini,
.iPhone13Pro,
.iPhone13ProMax,
.iPhone14,
.iPhone14Plus,
.iPhone14Pro,
.iPhone14ProMax,
.iPhone15,
.iPhone15Plus,
.iPhone15Pro,
.iPhone15ProMax
]
}
case iPodTouch1
case iPodTouch2
case iPodTouch3
case iPodTouch4
case iPodTouch5
case iPodTouch6
case iPodTouch7
case iPhone
case iPhone3G
case iPhone3GS
case iPhone4
case iPhone4S
case iPhone5
case iPhone5C
case iPhone5S
case iPhone6
case iPhone6Plus
case iPhone6S
case iPhone6SPlus
case iPhoneSE
case iPhone7
case iPhone7Plus
case iPhone8
case iPhone8Plus
case iPhoneX
case iPhoneXS
case iPhoneXSMax
case iPhoneXR
case iPhone11
case iPhone11Pro
case iPhone11ProMax
case iPhoneSE2ndGen
case iPhone12
case iPhone12Mini
case iPhone12Pro
case iPhone12ProMax
case iPhone13
case iPhone13Mini
case iPhone13Pro
case iPhone13ProMax
case iPhoneSE3rdGen
case iPhone14
case iPhone14Plus
case iPhone14Pro
case iPhone14ProMax
case iPhone15
case iPhone15Plus
case iPhone15Pro
case iPhone15ProMax
case unknown(String)
public var modelId: [String] {
switch self {
case .iPodTouch1:
return ["iPod1,1"]
case .iPodTouch2:
return ["iPod2,1"]
case .iPodTouch3:
return ["iPod3,1"]
case .iPodTouch4:
return ["iPod4,1"]
case .iPodTouch5:
return ["iPod5,1"]
case .iPodTouch6:
return ["iPod7,1"]
case .iPodTouch7:
return ["iPod9,1"]
case .iPhone:
return ["iPhone1,1"]
case .iPhone3G:
return ["iPhone1,2"]
case .iPhone3GS:
return ["iPhone2,1"]
case .iPhone4:
return ["iPhone3,1", "iPhone3,2", "iPhone3,3"]
case .iPhone4S:
return ["iPhone4,1", "iPhone4,2", "iPhone4,3"]
case .iPhone5:
return ["iPhone5,1", "iPhone5,2"]
case .iPhone5C:
return ["iPhone5,3", "iPhone5,4"]
case .iPhone5S:
return ["iPhone6,1", "iPhone6,2"]
case .iPhone6:
return ["iPhone7,2"]
case .iPhone6Plus:
return ["iPhone7,1"]
case .iPhone6S:
return ["iPhone8,1"]
case .iPhone6SPlus:
return ["iPhone8,2"]
case .iPhoneSE:
return ["iPhone8,4"]
case .iPhone7:
return ["iPhone9,1", "iPhone9,3"]
case .iPhone7Plus:
return ["iPhone9,2", "iPhone9,4"]
case .iPhone8:
return ["iPhone10,1", "iPhone10,4"]
case .iPhone8Plus:
return ["iPhone10,2", "iPhone10,5"]
case .iPhoneX:
return ["iPhone10,3", "iPhone10,6"]
case .iPhoneXS:
return ["iPhone11,2"]
case .iPhoneXSMax:
return ["iPhone11,4", "iPhone11,6"]
case .iPhoneXR:
return ["iPhone11,8"]
case .iPhone11:
return ["iPhone12,1"]
case .iPhone11Pro:
return ["iPhone12,3"]
case .iPhone11ProMax:
return ["iPhone12,5"]
case .iPhoneSE2ndGen:
return ["iPhone12,8"]
case .iPhone12:
return ["iPhone13,2"]
case .iPhone12Mini:
return ["iPhone13,1"]
case .iPhone12Pro:
return ["iPhone13,3"]
case .iPhone12ProMax:
return ["iPhone13,4"]
case .iPhone13:
return ["iPhone14,5"]
case .iPhone13Mini:
return ["iPhone14,4"]
case .iPhone13Pro:
return ["iPhone14,2"]
case .iPhone13ProMax:
return ["iPhone14,3"]
case .iPhoneSE3rdGen:
return ["iPhone14,6"]
case .iPhone14:
return ["iPhone14,7"]
case .iPhone14Plus:
return ["iPhone14,8"]
case .iPhone14Pro:
return ["iPhone15,2"]
case .iPhone14ProMax:
return ["iPhone15,3"]
case .iPhone15:
return ["iPhone15,4"]
case .iPhone15Plus:
return ["iPhone15,5"]
case .iPhone15Pro:
return ["iPhone16,1"]
case .iPhone15ProMax:
return ["iPhone16,2"]
case let .unknown(modelId):
return [modelId]
}
}
public var modelName: String {
switch self {
case .iPodTouch1:
return "iPod touch 1G"
case .iPodTouch2:
return "iPod touch 2G"
case .iPodTouch3:
return "iPod touch 3G"
case .iPodTouch4:
return "iPod touch 4G"
case .iPodTouch5:
return "iPod touch 5G"
case .iPodTouch6:
return "iPod touch 6G"
case .iPodTouch7:
return "iPod touch 7G"
case .iPhone:
return "iPhone"
case .iPhone3G:
return "iPhone 3G"
case .iPhone3GS:
return "iPhone 3GS"
case .iPhone4:
return "iPhone 4"
case .iPhone4S:
return "iPhone 4S"
case .iPhone5:
return "iPhone 5"
case .iPhone5C:
return "iPhone 5C"
case .iPhone5S:
return "iPhone 5S"
case .iPhone6:
return "iPhone 6"
case .iPhone6Plus:
return "iPhone 6 Plus"
case .iPhone6S:
return "iPhone 6S"
case .iPhone6SPlus:
return "iPhone 6S Plus"
case .iPhoneSE:
return "iPhone SE"
case .iPhone7:
return "iPhone 7"
case .iPhone7Plus:
return "iPhone 7 Plus"
case .iPhone8:
return "iPhone 8"
case .iPhone8Plus:
return "iPhone 8 Plus"
case .iPhoneX:
return "iPhone X"
case .iPhoneXS:
return "iPhone XS"
case .iPhoneXSMax:
return "iPhone XS Max"
case .iPhoneXR:
return "iPhone XR"
case .iPhone11:
return "iPhone 11"
case .iPhone11Pro:
return "iPhone 11 Pro"
case .iPhone11ProMax:
return "iPhone 11 Pro Max"
case .iPhoneSE2ndGen:
return "iPhone SE (2nd gen)"
case .iPhone12:
return "iPhone 12"
case .iPhone12Mini:
return "iPhone 12 mini"
case .iPhone12Pro:
return "iPhone 12 Pro"
case .iPhone12ProMax:
return "iPhone 12 Pro Max"
case .iPhone13:
return "iPhone 13"
case .iPhone13Mini:
return "iPhone 13 mini"
case .iPhone13Pro:
return "iPhone 13 Pro"
case .iPhone13ProMax:
return "iPhone 13 Pro Max"
case .iPhoneSE3rdGen:
return "iPhone SE (3rd gen)"
case .iPhone14:
return "iPhone 14"
case .iPhone14Plus:
return "iPhone 14 Plus"
case .iPhone14Pro:
return "iPhone 14 Pro"
case .iPhone14ProMax:
return "iPhone 14 Pro Max"
case .iPhone15:
return "iPhone 15"
case .iPhone15Plus:
return "iPhone 15 Plus"
case .iPhone15Pro:
return "iPhone 15 Pro"
case .iPhone15ProMax:
return "iPhone 15 Pro Max"
case let .unknown(modelId):
if modelId.hasPrefix("iPhone") {
return "Unknown iPhone"
} else if modelId.hasPrefix("iPod") {
return "Unknown iPod"
} else if modelId.hasPrefix("iPad") {
return "Unknown iPad"
} else {
return "Unknown Device"
}
}
}
public var isIpad: Bool {
return self.modelId.first?.hasPrefix("iPad") ?? false
}
public static let current = DeviceModel()
private init() {
var systemInfo = utsname()
uname(&systemInfo)
let modelCode = withUnsafePointer(to: &systemInfo.machine) {
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
ptr in String.init(validatingUTF8: ptr)
}
}
var result: DeviceModel?
if let modelCode {
for model in DeviceModel.allCases {
if model.modelId.contains(modelCode) {
result = model
break
}
}
}
if let result {
self = result
} else {
self = .unknown(modelCode ?? "")
}
}
}

View file

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

View file

@ -29,6 +29,7 @@ import ShareController
import UndoUI
import AvatarNode
import OverlayStatusController
import TelegramUIPreferences
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
@ -333,9 +334,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let parsedUrl = URL(string: url) {
self.webView?.load(URLRequest(url: parsedUrl))
}
self.checkBotIdAndUrl(url)
if let keepAliveSignal = controller.keepAliveSignal {
self.keepAliveDisposable = (keepAliveSignal
|> deliverOnMainQueue).start(error: { [weak self] _ in
@ -355,7 +353,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let strongSelf = self else {
return
}
strongSelf.checkBotIdAndUrl(result.url)
if let parsedUrl = URL(string: result.url) {
strongSelf.queryId = result.queryId
strongSelf.webView?.load(URLRequest(url: parsedUrl))
@ -368,17 +365,16 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let self, let controller = self.controller else {
return
}
guard case let .peer(peer, params) = result, let peer, case let .withBotApp(appStart) = params else {
guard case let .peer(peer, params) = result, let peer, case let .withBotApp(appStart) = params, let botApp = appStart.botApp else {
controller.dismiss()
return
}
let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: appStart.botApp.id, accessHash: appStart.botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true)
let _ = (self.context.engine.messages.requestAppWebView(peerId: peer.id, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: appStart.payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), compact: appStart.compact, allowWrite: true)
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
guard let self, let parsedUrl = URL(string: result.url) else {
return
}
self.checkBotIdAndUrl(result.url)
self.controller?.titleView?.title = WebAppTitle(title: appStart.botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified)
self.controller?.titleView?.title = WebAppTitle(title: botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified)
self.webView?.load(URLRequest(url: parsedUrl))
})
})
@ -390,9 +386,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
strongSelf.queryId = result.queryId
strongSelf.webView?.load(URLRequest(url: parsedUrl))
strongSelf.checkBotIdAndUrl(result.url)
if let keepAliveSignal = result.keepAliveSignal {
strongSelf.keepAliveDisposable = (keepAliveSignal
|> deliverOnMainQueue).start(error: { [weak self] _ in
@ -412,14 +406,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
}
func checkBotIdAndUrl(_ url: String) {
// if url.hasPrefix("https://walletbot.me"), let botId = self.controller?.botId.id._internalGetInt64Value(), botId != 1985737506 {
// let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: "Bot id mismatch, please report steps to app developer", actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
// })])
// self.controller?.present(alertController, in: .window(.root))
// }
}
@objc fileprivate func mainButtonPressed() {
if let mainButtonState = self.mainButtonState, !mainButtonState.isVisible || !mainButtonState.isEnabled {
return
@ -845,7 +831,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
switch result {
case let .instantView(webPage, anchor):
let controller = InstantPageController(context: strongSelf.context, webPage: webPage, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .otherPrivate), anchor: anchor)
let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .otherPrivate))
strongSelf.controller?.getNavigationController()?.pushViewController(controller)
default:
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
@ -1164,15 +1150,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
transitionOut: nil
)
let controller = self.context.sharedContext.makeStoryMediaEditorScreen(context: self.context, source: source, text: text, link: linkUrl.flatMap { ($0, linkName) }, completion: { result, commit in
// let targetPeerId: EnginePeer.Id
let target: Stories.PendingTarget
// if let sendAsPeerId = result.options.sendAsPeerId {
// target = .peer(sendAsPeerId)
// targetPeerId = sendAsPeerId
// } else {
target = .myStories
// targetPeerId = self.context.account.peerId
// }
let target: Stories.PendingTarget = result.target
externalState.storyTarget = target
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {