mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Local instant view
This commit is contained in:
parent
0d1366e5cc
commit
5978a5278d
@ -41,6 +41,8 @@ final class BrowserContentState: Equatable {
|
||||
let contentType: ContentType
|
||||
let favicon: UIImage?
|
||||
let isSecure: Bool
|
||||
let hasInstantView: Bool
|
||||
let isInnerInstantViewEnabled: Bool
|
||||
|
||||
let canGoBack: Bool
|
||||
let canGoForward: Bool
|
||||
@ -56,6 +58,8 @@ final class BrowserContentState: Equatable {
|
||||
contentType: ContentType,
|
||||
favicon: UIImage? = nil,
|
||||
isSecure: Bool = false,
|
||||
hasInstantView: Bool = false,
|
||||
isInnerInstantViewEnabled: Bool = false,
|
||||
canGoBack: Bool = false,
|
||||
canGoForward: Bool = false,
|
||||
backList: [HistoryItem] = [],
|
||||
@ -68,6 +72,8 @@ final class BrowserContentState: Equatable {
|
||||
self.contentType = contentType
|
||||
self.favicon = favicon
|
||||
self.isSecure = isSecure
|
||||
self.hasInstantView = hasInstantView
|
||||
self.isInnerInstantViewEnabled = isInnerInstantViewEnabled
|
||||
self.canGoBack = canGoBack
|
||||
self.canGoForward = canGoForward
|
||||
self.backList = backList
|
||||
@ -96,6 +102,9 @@ final class BrowserContentState: Equatable {
|
||||
if lhs.isSecure != rhs.isSecure {
|
||||
return false
|
||||
}
|
||||
if lhs.hasInstantView != rhs.hasInstantView {
|
||||
return false
|
||||
}
|
||||
if lhs.canGoBack != rhs.canGoBack {
|
||||
return false
|
||||
}
|
||||
@ -112,43 +121,51 @@ final class BrowserContentState: Equatable {
|
||||
}
|
||||
|
||||
func withUpdatedTitle(_ title: String) -> BrowserContentState {
|
||||
return BrowserContentState(title: title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedUrl(_ url: String) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedIsSecure(_ isSecure: Bool) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedHasInstantView(_ hasInstantView: Bool) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedIsInnerInstantViewEnabled(_ isInnerInstantViewEnabled: Bool) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedEstimatedProgress(_ estimatedProgress: Double) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedReadingProgress(_ readingProgress: Double) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedFavicon(_ favicon: UIImage?) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedCanGoBack(_ canGoBack: Bool) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedCanGoForward(_ canGoForward: Bool) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: canGoForward, backList: self.backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedBackList(_ backList: [HistoryItem]) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: backList, forwardList: self.forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: backList, forwardList: self.forwardList)
|
||||
}
|
||||
|
||||
func withUpdatedForwardList(_ forwardList: [HistoryItem]) -> BrowserContentState {
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: forwardList)
|
||||
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, readingProgress: self.readingProgress, contentType: self.contentType, favicon: self.favicon, isSecure: self.isSecure, hasInstantView: self.hasInstantView, isInnerInstantViewEnabled: self.isInnerInstantViewEnabled, canGoBack: self.canGoBack, canGoForward: self.canGoForward, backList: self.backList, forwardList: forwardList)
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +175,7 @@ protocol BrowserContent: UIView {
|
||||
var currentState: BrowserContentState { get }
|
||||
var state: Signal<BrowserContentState, NoError> { get }
|
||||
|
||||
var pushContent: (BrowserScreen.Subject) -> Void { get set }
|
||||
var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void { get set }
|
||||
var present: (ViewController, Any?) -> Void { get set }
|
||||
var presentInGlobalOverlay: (ViewController) -> Void { get set }
|
||||
var getNavigationController: () -> NavigationController? { get set }
|
||||
@ -177,6 +194,8 @@ protocol BrowserContent: UIView {
|
||||
func navigateForward()
|
||||
func navigateTo(historyItem: BrowserContentState.HistoryItem)
|
||||
|
||||
func toggleInstantView(_ enabled: Bool)
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData)
|
||||
func updateFontState(_ state: BrowserPresentationState.FontState)
|
||||
|
||||
|
@ -36,7 +36,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
||||
return self.statePromise.get()
|
||||
}
|
||||
|
||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||
var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
|
||||
var openAppUrl: (String) -> Void = { _ in }
|
||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||
var minimize: () -> Void = { }
|
||||
@ -122,6 +122,9 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
||||
self.webView.evaluateJavaScript(js) { _, _ in }
|
||||
}
|
||||
|
||||
func toggleInstantView(_ enabled: Bool) {
|
||||
}
|
||||
|
||||
private var didSetupSearch = false
|
||||
private func setupSearch(completion: @escaping () -> Void) {
|
||||
guard !self.didSetupSearch else {
|
||||
@ -385,7 +388,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
||||
navigationController._keepModalDismissProgress = true
|
||||
navigationController.pushViewController(controller)
|
||||
} else {
|
||||
self.pushContent(subject)
|
||||
self.pushContent(subject, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
private var theme: InstantPageTheme
|
||||
private var settings: InstantPagePresentationSettings = .defaultSettings
|
||||
private let sourceLocation: InstantPageSourceLocation
|
||||
private let preloadedResouces: [Any]?
|
||||
private var originalContent: BrowserContent?
|
||||
|
||||
private var webPage: TelegramMediaWebpage?
|
||||
|
||||
@ -66,7 +68,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
|
||||
var currentAccessibilityAreas: [AccessibilityAreaNode] = []
|
||||
|
||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||
var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
|
||||
var restoreContent: (BrowserContent) -> Void = { _ in }
|
||||
var openAppUrl: (String) -> Void = { _ in }
|
||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||
var minimize: () -> Void = { }
|
||||
@ -91,12 +94,14 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
private var containerLayout: (size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets)?
|
||||
private var setupScrollOffsetOnLayout = false
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation) {
|
||||
init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation, preloadedResouces: [Any]?, originalContent: BrowserContent? = nil) {
|
||||
self.context = context
|
||||
self.webPage = webPage
|
||||
self.presentationData = presentationData
|
||||
self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: .defaultSettings)
|
||||
self.sourceLocation = sourceLocation
|
||||
self.preloadedResouces = preloadedResouces
|
||||
self.originalContent = originalContent
|
||||
|
||||
self.uuid = UUID()
|
||||
|
||||
@ -107,7 +112,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
title = ""
|
||||
}
|
||||
|
||||
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, readingProgress: 0.0, contentType: .instantPage)
|
||||
let isInnerInstantViewEnabled = originalContent != nil
|
||||
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, readingProgress: 0.0, contentType: .instantPage, isInnerInstantViewEnabled: isInnerInstantViewEnabled)
|
||||
self.statePromise = Promise<BrowserContentState>(self._state)
|
||||
|
||||
self.wrapperNode = ASDisplayNode()
|
||||
@ -126,7 +132,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
self.readingProgress.get()
|
||||
)
|
||||
|> map { estimatedProgress, readingProgress in
|
||||
return BrowserContentState(title: title, url: url, estimatedProgress: estimatedProgress, readingProgress: readingProgress, contentType: .instantPage)
|
||||
return BrowserContentState(title: title, url: url, estimatedProgress: estimatedProgress, readingProgress: readingProgress, contentType: .instantPage, isInnerInstantViewEnabled: isInnerInstantViewEnabled)
|
||||
}
|
||||
))
|
||||
|
||||
@ -360,6 +366,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
|
||||
}
|
||||
|
||||
func toggleInstantView(_ enabled: Bool) {
|
||||
if !enabled, let originalContent = self.originalContent {
|
||||
self.restoreContent(originalContent)
|
||||
}
|
||||
}
|
||||
|
||||
func setSearch(_ query: String?, completion: ((Int) -> Void)?) {
|
||||
|
||||
}
|
||||
@ -603,7 +615,22 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
self?.updateWebEmbedHeight(embedIndex, height)
|
||||
}, updateDetailsExpanded: { [weak self] expanded in
|
||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||
}, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: { [weak self] url in
|
||||
if let preloadedResouces = self?.preloadedResouces {
|
||||
var cleanUrl = url
|
||||
var components = URLComponents(string: url)
|
||||
components?.queryItems = nil
|
||||
cleanUrl = components?.url?.absoluteString ?? cleanUrl
|
||||
for resource in preloadedResouces {
|
||||
if let resource = resource as? [String: Any], let resourceUrl = resource["WebResourceURL"] as? String {
|
||||
if resourceUrl == url || resourceUrl.hasPrefix(cleanUrl) {
|
||||
return resource["WebResourceData"] as? Data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}) {
|
||||
newNode.frame = itemFrame
|
||||
newNode.updateLayout(size: itemFrame.size, transition: transition)
|
||||
if let topNode = topNode {
|
||||
@ -878,7 +905,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
|
||||
}
|
||||
|
||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl, let anchor = anchor {
|
||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl || baseUrl.isEmpty, let anchor = anchor {
|
||||
self.scrollToAnchor(anchor)
|
||||
return
|
||||
}
|
||||
@ -905,7 +932,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
case let .result(webpageResult):
|
||||
if let webpageResult = webpageResult, case .Loaded = webpageResult.webpage.content {
|
||||
strongSelf.loadProgress.set(1.0)
|
||||
strongSelf.pushContent(.instantPage(webPage: webpageResult.webpage, anchor: anchor, sourceLocation: strongSelf.sourceLocation))
|
||||
strongSelf.pushContent(.instantPage(webPage: webpageResult.webpage, anchor: anchor, sourceLocation: strongSelf.sourceLocation, preloadedResources: nil), nil)
|
||||
}
|
||||
break
|
||||
case let .progress(progress):
|
||||
@ -915,11 +942,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
}))
|
||||
} else {
|
||||
strongSelf.loadProgress.set(1.0)
|
||||
strongSelf.pushContent(.webPage(url: externalUrl))
|
||||
strongSelf.pushContent(.webPage(url: externalUrl), nil)
|
||||
}
|
||||
case let .instantView(webpage, anchor):
|
||||
strongSelf.loadProgress.set(1.0)
|
||||
strongSelf.pushContent(.instantPage(webPage: webpage, anchor: anchor, sourceLocation: strongSelf.sourceLocation))
|
||||
strongSelf.pushContent(.instantPage(webPage: webpage, anchor: anchor, sourceLocation: strongSelf.sourceLocation, preloadedResources: nil), nil)
|
||||
default:
|
||||
strongSelf.loadProgress.set(1.0)
|
||||
strongSelf.minimize()
|
||||
|
@ -25,6 +25,11 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
private let pdfView: PDFView
|
||||
private let scrollView: UIScrollView!
|
||||
|
||||
private let pageIndicatorBackgorund: UIVisualEffectView
|
||||
private let pageIndicator = ComponentView<Empty>()
|
||||
private var pageNumber: (Int, Int)?
|
||||
private var pageTimer: SwiftSignalKit.Timer?
|
||||
|
||||
let uuid: UUID
|
||||
|
||||
private var _state: BrowserContentState
|
||||
@ -37,7 +42,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
return self.statePromise.get()
|
||||
}
|
||||
|
||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||
var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
|
||||
var openAppUrl: (String) -> Void = { _ in }
|
||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||
var minimize: () -> Void = { }
|
||||
@ -57,6 +62,10 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
self.pdfView = PDFView()
|
||||
self.pdfView.clipsToBounds = false
|
||||
|
||||
self.pageIndicatorBackgorund = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||
self.pageIndicatorBackgorund.clipsToBounds = true
|
||||
self.pageIndicatorBackgorund.layer.cornerRadius = 10.0
|
||||
|
||||
var scrollView: UIScrollView?
|
||||
for view in self.pdfView.subviews {
|
||||
if let view = view as? UIScrollView {
|
||||
@ -104,6 +113,10 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
scrollView.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
self.pageNumber = (1, self.pdfView.document?.pageCount ?? 1)
|
||||
|
||||
self.startPageIndicatorTimer()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -120,6 +133,18 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
}
|
||||
}
|
||||
|
||||
func startPageIndicatorTimer() {
|
||||
self.pageTimer?.invalidate()
|
||||
|
||||
self.pageTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.25)
|
||||
transition.setAlpha(view: self.pageIndicatorBackgorund, alpha: 0.0)
|
||||
}, queue: Queue.mainQueue())
|
||||
self.pageTimer?.start()
|
||||
}
|
||||
|
||||
func updateFontState(_ state: BrowserPresentationState.FontState) {
|
||||
|
||||
@ -127,6 +152,9 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) {
|
||||
}
|
||||
|
||||
func toggleInstantView(_ enabled: Bool) {
|
||||
}
|
||||
|
||||
private var findSession: Any?
|
||||
private var previousQuery: String?
|
||||
private var currentSearchResult: Int = 0
|
||||
@ -308,6 +336,29 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
let pdfViewFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - bottomInset))
|
||||
transition.setFrame(view: self.pdfView, frame: pdfViewFrame)
|
||||
|
||||
let pageIndicatorSize = self.pageIndicator.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
Text(text: "\(self.pageNumber?.0 ?? 1) of \(self.pageNumber?.1 ?? 1)", font: Font.with(size: 15.0, weight: .semibold, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemSecondaryTextColor)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
if let view = self.pageIndicator.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(self.pageIndicatorBackgorund)
|
||||
self.pageIndicatorBackgorund.contentView.addSubview(view)
|
||||
}
|
||||
|
||||
let horizontalPadding: CGFloat = 10.0
|
||||
let verticalPadding: CGFloat = 8.0
|
||||
let pageBackgroundFrame = CGRect(origin: CGPoint(x: insets.left + 20.0, y: insets.top + 16.0), size: CGSize(width: horizontalPadding * 2.0 + pageIndicatorSize.width, height: verticalPadding * 2.0 + pageIndicatorSize.height))
|
||||
|
||||
self.pageIndicatorBackgorund.bounds = CGRect(origin: .zero, size: pageBackgroundFrame.size)
|
||||
transition.setPosition(view: self.pageIndicatorBackgorund, position: pageBackgroundFrame.center)
|
||||
view.frame = CGRect(origin: CGPoint(x: horizontalPadding, y: verticalPadding), size: pageIndicatorSize)
|
||||
}
|
||||
|
||||
if isFirstTime {
|
||||
self.pdfView.setNeedsLayout()
|
||||
self.pdfView.layoutIfNeeded()
|
||||
@ -370,9 +421,31 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
}
|
||||
if !scrollView.isZooming && !self.wasZooming {
|
||||
self.updateScrollingOffset(isReset: false, transition: .immediate)
|
||||
|
||||
if let document = self.pdfView.document, let page = self.pdfView.currentPage {
|
||||
let number = document.index(for: page) + 1
|
||||
if number != self.pageNumber?.0 {
|
||||
self.pageNumber = (number, document.pageCount)
|
||||
if let (size, insets, fullInsets) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
if let scrollViewDelegate = scrollView as? UIScrollViewDelegate {
|
||||
scrollViewDelegate.scrollViewWillBeginDragging?(scrollView)
|
||||
}
|
||||
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.1)
|
||||
transition.setAlpha(view: self.pageIndicatorBackgorund, alpha: 1.0)
|
||||
|
||||
self.pageTimer?.invalidate()
|
||||
self.pageTimer = nil
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if let scrollViewDelegate = scrollView as? UIScrollViewDelegate {
|
||||
scrollViewDelegate.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
|
||||
@ -383,6 +456,8 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
if self.ignoreUpdatesUntilScrollingStopped {
|
||||
self.ignoreUpdatesUntilScrollingStopped = false
|
||||
}
|
||||
|
||||
self.startPageIndicatorTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +470,8 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
if self.ignoreUpdatesUntilScrollingStopped {
|
||||
self.ignoreUpdatesUntilScrollingStopped = false
|
||||
}
|
||||
|
||||
self.startPageIndicatorTimer()
|
||||
}
|
||||
|
||||
private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) {
|
||||
@ -449,7 +526,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
|
||||
navigationController._keepModalDismissProgress = true
|
||||
navigationController.pushViewController(controller)
|
||||
} else {
|
||||
self.pushContent(subject)
|
||||
self.pushContent(subject, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
585
submodules/BrowserUI/Sources/BrowserReadability.swift
Normal file
585
submodules/BrowserUI/Sources/BrowserReadability.swift
Normal file
@ -0,0 +1,585 @@
|
||||
import Foundation
|
||||
import WebKit
|
||||
import AppBundle
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import InstantPageUI
|
||||
|
||||
public class Readability: NSObject, WKNavigationDelegate {
|
||||
private let url: URL
|
||||
let webView: WKWebView
|
||||
private let completionHandler: ((_ webPage: (TelegramMediaWebpage, [Any]?)?, _ error: Error?) -> Void)
|
||||
private var hasRenderedReadabilityHTML = false
|
||||
|
||||
private var subresources: [Any]?
|
||||
|
||||
init(url: URL, archiveData: Data, completionHandler: @escaping (_ webPage: (TelegramMediaWebpage, [Any]?)?, _ error: Error?) -> Void) {
|
||||
self.url = url
|
||||
self.completionHandler = completionHandler
|
||||
|
||||
let preferences = WKPreferences()
|
||||
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.preferences = preferences
|
||||
configuration.userContentController.addUserScript(ReadabilityUserScript())
|
||||
|
||||
self.webView = WKWebView(frame: CGRect.zero, configuration: configuration)
|
||||
|
||||
super.init()
|
||||
|
||||
self.webView.configuration.suppressesIncrementalRendering = true
|
||||
self.webView.navigationDelegate = self
|
||||
if #available(iOS 16.4, *) {
|
||||
self.webView.isInspectable = true
|
||||
}
|
||||
|
||||
if let (html, subresources) = extractHtmlString(from: archiveData) {
|
||||
self.subresources = subresources
|
||||
self.webView.loadHTMLString(html, baseURL: url.baseURL)
|
||||
}
|
||||
}
|
||||
|
||||
private func initializeReadability(completion: @escaping (_ result: TelegramMediaWebpage?, _ error: Error?) -> Void) {
|
||||
guard let readabilityInitializationJS = loadFile(name: "ReaderMode", type: "js") else {
|
||||
return
|
||||
}
|
||||
|
||||
self.webView.evaluateJavaScript(readabilityInitializationJS) { (result, error) in
|
||||
guard let result = result as? [String: Any] else {
|
||||
completion(nil, error)
|
||||
return
|
||||
}
|
||||
guard let page = parseJson(result, url: self.url.absoluteString) else {
|
||||
return
|
||||
}
|
||||
completion(page, nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
if !self.hasRenderedReadabilityHTML {
|
||||
self.initializeReadability() { [weak self] (webPage: TelegramMediaWebpage?, error: Error?) in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.hasRenderedReadabilityHTML = true
|
||||
guard let webPage else {
|
||||
self.completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
self.completionHandler((webPage, self.subresources), error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReadabilityUserScript: WKUserScript {
|
||||
convenience override init() {
|
||||
guard let js = loadFile(name: "Readability", type: "js") else {
|
||||
fatalError()
|
||||
}
|
||||
self.init(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
|
||||
}
|
||||
}
|
||||
|
||||
func loadFile(name: String, type: String) -> String? {
|
||||
let bundle = getAppBundle()
|
||||
guard let userScriptPath = bundle.path(forResource: name, ofType: type) else {
|
||||
return nil
|
||||
}
|
||||
guard let userScriptData = try? Data(contentsOf: URL(fileURLWithPath: userScriptPath)) else {
|
||||
return nil
|
||||
}
|
||||
guard let userScript = String(data: userScriptData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return userScript
|
||||
}
|
||||
|
||||
private func extractHtmlString(from webArchiveData: Data) -> (String, [Any]?)? {
|
||||
if let webArchiveDict = try? PropertyListSerialization.propertyList(from: webArchiveData, format: nil) as? [String: Any],
|
||||
let mainResource = webArchiveDict["WebMainResource"] as? [String: Any],
|
||||
let htmlData = mainResource["WebResourceData"] as? Data {
|
||||
|
||||
guard let htmlString = String(data: htmlData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return (htmlString, webArchiveDict["WebSubresources"] as? [Any])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func parseJson(_ input: [String: Any], url: String) -> TelegramMediaWebpage? {
|
||||
let siteName = input["siteName"] as? String
|
||||
let title = input["title"] as? String
|
||||
let byline = input["byline"] as? String
|
||||
let excerpt = input["excerpt"] as? String
|
||||
|
||||
var media: [MediaId: Media] = [:]
|
||||
let blocks = parseContent(input, url, &media)
|
||||
|
||||
guard !blocks.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaWebpage(
|
||||
webpageId: MediaId(namespace: 0, id: 0),
|
||||
content: .Loaded(
|
||||
TelegramMediaWebpageLoadedContent(
|
||||
url: url,
|
||||
displayUrl: url,
|
||||
hash: 0,
|
||||
type: "article",
|
||||
websiteName: siteName,
|
||||
title: title,
|
||||
text: excerpt,
|
||||
embedUrl: nil,
|
||||
embedType: nil,
|
||||
embedSize: nil,
|
||||
duration: nil,
|
||||
author: byline,
|
||||
isMediaLargeByDefault: nil,
|
||||
image: nil,
|
||||
file: nil,
|
||||
story: nil,
|
||||
attributes: [],
|
||||
instantPage: InstantPage(
|
||||
blocks: blocks,
|
||||
media: media,
|
||||
isComplete: true,
|
||||
rtl: false,
|
||||
url: url,
|
||||
views: nil
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private func parseContent(_ input: [String: Any], _ url: String, _ media: inout [MediaId: Media]) -> [InstantPageBlock] {
|
||||
let title = input["title"] as? String
|
||||
let byline = input["byline"] as? String
|
||||
let date = input["publishedTime"] as? String
|
||||
|
||||
let _ = date
|
||||
|
||||
guard let content = input["content"] as? [Any] else {
|
||||
return []
|
||||
}
|
||||
var blocks = parsePageBlocks(content, url, &media)
|
||||
if case .header = blocks.first {
|
||||
} else {
|
||||
if var byline {
|
||||
byline = byline.replacingOccurrences(of: "[\n\t]+", with: " ", options: .regularExpression, range: nil)
|
||||
blocks.insert(.authorDate(author: trim(parseRichText(byline)), date: 0), at: 0)
|
||||
}
|
||||
if let title {
|
||||
blocks.insert(.title(trim(parseRichText(title))), at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
return blocks
|
||||
}
|
||||
|
||||
private func parseRichText(_ input: String) -> RichText {
|
||||
return .plain(input)
|
||||
}
|
||||
|
||||
private func parseRichText(_ input: [String: Any], _ media: inout [MediaId: Media]) -> RichText {
|
||||
var text: RichText
|
||||
if let string = input["content"] as? String {
|
||||
text = parseRichText(string)
|
||||
} else if let array = input["content"] as? [Any] {
|
||||
text = parseRichText(array, &media)
|
||||
} else {
|
||||
text = .empty
|
||||
}
|
||||
text = applyAnchor(text, item: input)
|
||||
if let _ = input["bold"] {
|
||||
text = .bold(text)
|
||||
}
|
||||
if let _ = input["italic"] {
|
||||
text = .italic(text)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> RichText {
|
||||
var result: [RichText] = []
|
||||
|
||||
for item in input {
|
||||
if let string = item as? String {
|
||||
result.append(parseRichText(string))
|
||||
} else if let item = item as? [String: Any], let tag = item["tag"] as? String {
|
||||
var text: RichText?
|
||||
switch tag {
|
||||
case "b", "strong":
|
||||
text = .bold(parseRichText(item, &media))
|
||||
case "i":
|
||||
text = .italic(parseRichText(item, &media))
|
||||
case "s":
|
||||
text = .strikethrough(parseRichText(item, &media))
|
||||
case "p":
|
||||
text = parseRichText(item, &media)
|
||||
case "a":
|
||||
if let href = item["href"] as? String {
|
||||
let telString = "tel:"
|
||||
let mailtoString = "mailto:"
|
||||
if href.hasPrefix("tel:") {
|
||||
text = .phone(text: parseRichText(item, &media), phone: String(href[href.index(href.startIndex, offsetBy: telString.distance(from: telString.startIndex, to: telString.endIndex))...]))
|
||||
} else if href.hasPrefix(mailtoString) {
|
||||
text = .email(text: parseRichText(item, &media), email: String(href[href.index(href.startIndex, offsetBy: mailtoString.distance(from: mailtoString.startIndex, to: mailtoString.endIndex))...]))
|
||||
} else {
|
||||
text = .url(text: parseRichText(item, &media), url: href, webpageId: nil)
|
||||
}
|
||||
} else {
|
||||
text = parseRichText(item, &media)
|
||||
}
|
||||
case "pre", "code":
|
||||
text = .fixed(parseRichText(item, &media))
|
||||
case "mark":
|
||||
text = .marked(parseRichText(item, &media))
|
||||
case "sub":
|
||||
text = .subscript(parseRichText(item, &media))
|
||||
case "sup":
|
||||
text = .superscript(parseRichText(item, &media))
|
||||
case "img":
|
||||
if let src = item["src"] as? String, !src.isEmpty {
|
||||
let width: Int32
|
||||
if let value = item["width"] as? String, let intValue = Int32(value) {
|
||||
width = intValue
|
||||
} else {
|
||||
width = 0
|
||||
}
|
||||
let height: Int32
|
||||
if let value = item["height"] as? String, let intValue = Int32(value) {
|
||||
height = intValue
|
||||
} else {
|
||||
height = 0
|
||||
}
|
||||
let id = MediaId(namespace: Namespaces.Media.CloudFile, id: Int64(media.count))
|
||||
media[id] = TelegramMediaImage(
|
||||
imageId: id,
|
||||
representations: [
|
||||
TelegramMediaImageRepresentation(
|
||||
dimensions: PixelDimensions(width: width, height: height),
|
||||
resource: InstantPageExternalMediaResource(url: src),
|
||||
progressiveSizes: [],
|
||||
immediateThumbnailData: nil
|
||||
)
|
||||
],
|
||||
immediateThumbnailData: nil,
|
||||
reference: nil,
|
||||
partialReference: nil,
|
||||
flags: []
|
||||
)
|
||||
text = .image(id: id, dimensions: PixelDimensions(width: width, height: height))
|
||||
}
|
||||
case "br":
|
||||
if let last = result.last {
|
||||
result[result.count - 1] = addNewLine(last)
|
||||
}
|
||||
default:
|
||||
text = parseRichText(item, &media)
|
||||
}
|
||||
if var text {
|
||||
text = applyAnchor(text, item: item)
|
||||
result.append(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !result.isEmpty {
|
||||
return .concat(result)
|
||||
} else if result.count == 1, let text = result.first {
|
||||
return text
|
||||
} else {
|
||||
return .empty
|
||||
}
|
||||
}
|
||||
|
||||
private func trimStart(_ input: RichText) -> RichText {
|
||||
return input
|
||||
}
|
||||
|
||||
private func trimEnd(_ input: RichText) -> RichText {
|
||||
return input
|
||||
}
|
||||
|
||||
private func trim(_ input: RichText) -> RichText {
|
||||
return input
|
||||
}
|
||||
|
||||
private func addNewLine(_ input: RichText) -> RichText {
|
||||
var text = input
|
||||
switch input {
|
||||
case .empty:
|
||||
text = .empty
|
||||
case let .plain(string):
|
||||
text = .plain(string + "\n")
|
||||
case let .bold(richText):
|
||||
text = .bold(addNewLine(richText))
|
||||
case let .italic(richText):
|
||||
text = .italic(addNewLine(richText))
|
||||
case let .underline(richText):
|
||||
text = .underline(addNewLine(richText))
|
||||
case let .strikethrough(richText):
|
||||
text = .strikethrough(addNewLine(richText))
|
||||
case let .fixed(richText):
|
||||
text = .fixed(addNewLine(richText))
|
||||
case let .url(richText, url, webpageId):
|
||||
text = .url(text: addNewLine(richText), url: url, webpageId: webpageId)
|
||||
case let .email(richText, email):
|
||||
text = .email(text: addNewLine(richText), email: email)
|
||||
case let .subscript(richText):
|
||||
text = .subscript(addNewLine(richText))
|
||||
case let .superscript(richText):
|
||||
text = .superscript(addNewLine(richText))
|
||||
case let .marked(richText):
|
||||
text = .marked(addNewLine(richText))
|
||||
case let .phone(richText, phone):
|
||||
text = .phone(text: addNewLine(richText), phone: phone)
|
||||
case let .anchor(richText, name):
|
||||
text = .anchor(text: addNewLine(richText), name: name)
|
||||
case var .concat(array):
|
||||
array[array.count - 1] = addNewLine(array[array.count - 1])
|
||||
text = .concat(array)
|
||||
case .image:
|
||||
break
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
private func applyAnchor(_ input: RichText, item: [String: Any]) -> RichText {
|
||||
guard let id = item["id"] as? String, !id.isEmpty else {
|
||||
return input
|
||||
}
|
||||
return .anchor(text: input, name: id)
|
||||
}
|
||||
|
||||
private func parseTable(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock {
|
||||
let title = (input["title"] as? String) ?? ""
|
||||
return .table(
|
||||
title: trim(applyAnchor(parseRichText(title), item: input)),
|
||||
rows: parseTableRows((input["content"] as? [Any]) ?? [], &media),
|
||||
bordered: true,
|
||||
striped: true
|
||||
)
|
||||
}
|
||||
|
||||
private func parseTableRows(_ input: [Any], _ media: inout [MediaId: Media]) -> [InstantPageTableRow] {
|
||||
var result: [InstantPageTableRow] = []
|
||||
for item in input {
|
||||
if let item = item as? [String: Any] {
|
||||
let tag = item["tag"] as? String
|
||||
if tag == "tr" {
|
||||
result.append(parseTableRow(item, &media))
|
||||
} else if let content = item["content"] as? [Any] {
|
||||
result.append(contentsOf: parseTableRows(content, &media))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func parseTableRow(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageTableRow {
|
||||
var cells: [InstantPageTableCell] = []
|
||||
|
||||
if let content = input["content"] as? [Any] {
|
||||
for item in content {
|
||||
guard let item = item as? [String: Any] else {
|
||||
continue
|
||||
}
|
||||
let tag = item["tag"] as? String
|
||||
guard ["td", "th"].contains(tag) else {
|
||||
continue
|
||||
}
|
||||
var text: RichText?
|
||||
if let content = item["content"] as? [Any] {
|
||||
text = trim(parseRichText(content, &media))
|
||||
if let currentText = text {
|
||||
if let _ = item["bold"] {
|
||||
text = .bold(currentText)
|
||||
}
|
||||
if let _ = item["italic"] {
|
||||
text = .italic(currentText)
|
||||
}
|
||||
}
|
||||
}
|
||||
cells.append(InstantPageTableCell(
|
||||
text: text,
|
||||
header: tag == "th",
|
||||
alignment: item["xcenter"] != nil ? .center : .left,
|
||||
verticalAlignment: .middle,
|
||||
colspan: ((item["colspan"] as? String).flatMap { Int32($0) }) ?? 0,
|
||||
rowspan: ((item["rowspan"] as? String).flatMap { Int32($0) }) ?? 0
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return InstantPageTableRow(cells: cells)
|
||||
}
|
||||
|
||||
private func parseDetails(_ item: [String: Any], _ url: String, _ media: inout [MediaId: Media]) -> InstantPageBlock? {
|
||||
guard var content = item["contant"] as? [Any] else {
|
||||
return nil
|
||||
}
|
||||
var title: RichText = .empty
|
||||
var titleIndex: Int?
|
||||
for i in 0 ..< content.count {
|
||||
if let subitem = content[i] as? [String: Any], let tag = subitem["tag"] as? String, tag == "summary" {
|
||||
title = trim(parseRichText(subitem, &media))
|
||||
titleIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if let titleIndex {
|
||||
content.remove(at: titleIndex)
|
||||
}
|
||||
return .details(
|
||||
title: title,
|
||||
blocks: parsePageBlocks(content, url, &media),
|
||||
expanded: item["open"] != nil
|
||||
)
|
||||
}
|
||||
|
||||
private func parseList(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
|
||||
guard let content = input["content"] as? [Any], let tag = input["tag"] as? String else {
|
||||
return nil
|
||||
}
|
||||
var items: [InstantPageListItem] = []
|
||||
for item in content {
|
||||
guard let item = item as? [String: Any], let tag = item["tag"] as? String, tag == "li" else {
|
||||
continue
|
||||
}
|
||||
items.append(.text(trim(parseRichText(item, &media)), nil))
|
||||
}
|
||||
let ordered = tag == "ol"
|
||||
return .list(items: items, ordered: ordered)
|
||||
}
|
||||
|
||||
private func parseImage(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
|
||||
guard let src = input["src"] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let caption: InstantPageCaption
|
||||
if let alt = input["alt"] as? String {
|
||||
caption = InstantPageCaption(
|
||||
text: trim(parseRichText(alt)),
|
||||
credit: .empty
|
||||
)
|
||||
} else {
|
||||
caption = InstantPageCaption(text: .empty, credit: .empty)
|
||||
}
|
||||
|
||||
let width: Int32
|
||||
if let value = input["width"] as? String, let intValue = Int32(value) {
|
||||
width = intValue
|
||||
} else {
|
||||
width = 0
|
||||
}
|
||||
|
||||
let height: Int32
|
||||
if let value = input["height"] as? String, let intValue = Int32(value) {
|
||||
height = intValue
|
||||
} else {
|
||||
height = 0
|
||||
}
|
||||
|
||||
let id = MediaId(namespace: Namespaces.Media.CloudImage, id: Int64(media.count))
|
||||
media[id] = TelegramMediaImage(
|
||||
imageId: id,
|
||||
representations: [
|
||||
TelegramMediaImageRepresentation(
|
||||
dimensions: PixelDimensions(width: width, height: height),
|
||||
resource: InstantPageExternalMediaResource(url: src),
|
||||
progressiveSizes: [],
|
||||
immediateThumbnailData: nil
|
||||
)
|
||||
],
|
||||
immediateThumbnailData: nil,
|
||||
reference: nil,
|
||||
partialReference: nil,
|
||||
flags: []
|
||||
)
|
||||
|
||||
return .image(
|
||||
id: id,
|
||||
caption: caption,
|
||||
url: nil,
|
||||
webpageId: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
|
||||
guard let content = input["content"] as? [Any] else {
|
||||
return nil
|
||||
}
|
||||
var block: InstantPageBlock?
|
||||
var caption: RichText?
|
||||
for item in content {
|
||||
if let item = item as? [String: Any], let tag = item["tag"] as? String {
|
||||
if tag == "img" {
|
||||
block = parseImage(item, &media)
|
||||
} else if tag == "figurecaption" {
|
||||
caption = trim(parseRichText(item, &media))
|
||||
}
|
||||
}
|
||||
}
|
||||
guard var block else {
|
||||
return nil
|
||||
}
|
||||
if let caption, case let .image(id, _, url, webpageId) = block {
|
||||
block = .image(id: id, caption: InstantPageCaption(text: caption, credit: .empty), url: url, webpageId: webpageId)
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [MediaId: Media]) -> [InstantPageBlock] {
|
||||
var result: [InstantPageBlock] = []
|
||||
for item in input {
|
||||
if let string = item as? String {
|
||||
result.append(.paragraph(parseRichText(string)))
|
||||
} else if let item = item as? [String: Any], let tag = item["tag"] as? String {
|
||||
let content = item["content"] as? [Any]
|
||||
switch tag {
|
||||
case "p":
|
||||
result.append(.paragraph(trim(parseRichText(item, &media))))
|
||||
case "h1", "h2":
|
||||
result.append(.header(trim(parseRichText(item, &media))))
|
||||
case "h3", "h4", "h5", "h6":
|
||||
result.append(.subheader(trim(parseRichText(item, &media))))
|
||||
case "pre":
|
||||
result.append(.preformatted(.fixed(trim(parseRichText(item, &media)))))
|
||||
case "blockquote":
|
||||
result.append(.blockQuote(text: .italic(trim(parseRichText(item, &media))), caption: .empty))
|
||||
case "img":
|
||||
if let image = parseImage(item, &media) {
|
||||
result.append(image)
|
||||
}
|
||||
break
|
||||
case "figure":
|
||||
if let figure = parseFigure(item, &media) {
|
||||
result.append(figure)
|
||||
}
|
||||
case "table":
|
||||
result.append(parseTable(item, &media))
|
||||
case "ul", "ol":
|
||||
if let list = parseList(item, &media) {
|
||||
result.append(list)
|
||||
}
|
||||
case "hr":
|
||||
result.append(.divider)
|
||||
case "details":
|
||||
if let details = parseDetails(item, url, &media) {
|
||||
result.append(details)
|
||||
}
|
||||
default:
|
||||
if let content {
|
||||
result.append(contentsOf: parsePageBlocks(content, url, &media))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -492,6 +492,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
case increaseFontSize
|
||||
case resetFontSize
|
||||
case updateFontIsSerif(Bool)
|
||||
case toggleInstantView(Bool)
|
||||
case addBookmark
|
||||
case openBookmarks
|
||||
case openAddressBar
|
||||
@ -755,6 +756,8 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
return updatedState
|
||||
})
|
||||
content.updateFontState(self.presentationState.fontState)
|
||||
case let .toggleInstantView(enabled):
|
||||
content.toggleInstantView(enabled)
|
||||
case .addBookmark:
|
||||
if let content = self.content.last {
|
||||
self.addBookmark(content.currentState.url, showArrow: true)
|
||||
@ -822,7 +825,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
self.requestLayout(transition: transition)
|
||||
}
|
||||
|
||||
func pushContent(_ content: BrowserScreen.Subject, transition: ComponentTransition) {
|
||||
func pushContent(_ content: BrowserScreen.Subject, additionalContent: BrowserContent? = nil, transition: ComponentTransition) {
|
||||
let browserContent: BrowserContent
|
||||
switch content {
|
||||
case let .webPage(url):
|
||||
@ -834,25 +837,37 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
}
|
||||
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)
|
||||
case let .instantPage(webPage, anchor, sourceLocation, preloadedResouces):
|
||||
let instantPageContent = BrowserInstantPageContent(context: self.context, presentationData: self.presentationData, webPage: webPage, anchor: anchor, url: webPage.content.url ?? "", sourceLocation: sourceLocation, preloadedResouces: preloadedResouces, originalContent: additionalContent)
|
||||
instantPageContent.openPeer = { [weak self] peer in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openPeer(peer)
|
||||
}
|
||||
instantPageContent.restoreContent = { [weak self, weak instantPageContent] content in
|
||||
guard let self, let instantPageContent else {
|
||||
return
|
||||
}
|
||||
self.pushBrowserContent(content, additionalContent: instantPageContent, transition: .easeInOut(duration: 0.3).withUserData(NavigationStackComponent<Empty>.CurlTransition.hide))
|
||||
}
|
||||
browserContent = instantPageContent
|
||||
case let .document(file, _):
|
||||
browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file)
|
||||
case let .pdfDocument(file, _):
|
||||
browserContent = BrowserPdfContent(context: self.context, presentationData: self.presentationData, file: file)
|
||||
}
|
||||
browserContent.pushContent = { [weak self] content in
|
||||
browserContent.pushContent = { [weak self] content, additionalContent in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.pushContent(content, transition: .spring(duration: 0.4))
|
||||
var transition: ComponentTransition
|
||||
if let _ = additionalContent {
|
||||
transition = .easeInOut(duration: 0.3).withUserData(NavigationStackComponent<Empty>.CurlTransition.show)
|
||||
} else {
|
||||
transition = .spring(duration: 0.4)
|
||||
}
|
||||
self.pushContent(content, additionalContent: additionalContent, transition: transition)
|
||||
}
|
||||
browserContent.openAppUrl = { [weak self] url in
|
||||
guard let self else {
|
||||
@ -896,7 +911,15 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
}
|
||||
}
|
||||
|
||||
self.content.append(browserContent)
|
||||
self.pushBrowserContent(browserContent, additionalContent: additionalContent, transition: transition)
|
||||
}
|
||||
|
||||
func pushBrowserContent(_ browserContent: BrowserContent, additionalContent: BrowserContent? = nil, transition: ComponentTransition) {
|
||||
if let additionalContent, let index = self.content.firstIndex(where: { $0 === additionalContent }) {
|
||||
self.content[index] = browserContent
|
||||
} else {
|
||||
self.content.append(browserContent)
|
||||
}
|
||||
self.requestLayout(transition: transition)
|
||||
|
||||
self.setupContentStateUpdates()
|
||||
@ -1050,16 +1073,20 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
return
|
||||
}
|
||||
|
||||
guard let controller = self.controller, let content = self.content.last else {
|
||||
return
|
||||
}
|
||||
|
||||
if let animationComponentView = referenceView.componentView.view as? LottieComponent.View {
|
||||
animationComponentView.playOnce()
|
||||
}
|
||||
|
||||
self.view.endEditing(true)
|
||||
|
||||
let checkIcon: (PresentationTheme) -> UIImage? = { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Check"), color: theme.contextMenu.primaryColor) }
|
||||
let emptyIcon: (PresentationTheme) -> UIImage? = { _ in
|
||||
return nil
|
||||
}
|
||||
// let checkIcon: (PresentationTheme) -> UIImage? = { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Check"), color: theme.contextMenu.primaryColor) }
|
||||
// let emptyIcon: (PresentationTheme) -> UIImage? = { _ in
|
||||
// return nil
|
||||
// }
|
||||
|
||||
let settings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings])
|
||||
|> take(1)
|
||||
@ -1071,17 +1098,21 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (settings
|
||||
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
||||
guard let self, let controller = self.controller, let contentState = self.contentState, let layout = self.validLayout?.0 else {
|
||||
return
|
||||
}
|
||||
let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view))
|
||||
|
||||
let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view))
|
||||
let items: Signal<ContextController.Items, NoError> = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
settings,
|
||||
content.state
|
||||
)
|
||||
|> map { [weak self] settings, contentState -> ContextController.Items in
|
||||
guard let self, let layout = self.validLayout?.0 else {
|
||||
return ContextController.Items(content: .list([]))
|
||||
}
|
||||
|
||||
let performAction = self.performAction
|
||||
|
||||
let forceIsSerif = self.presentationState.fontState.isSerif
|
||||
// let forceIsSerif = self.presentationState.fontState.isSerif
|
||||
let fontItem = BrowserFontSizeContextMenuItem(
|
||||
value: self.presentationState.fontState.size,
|
||||
decrease: { [weak self] in
|
||||
@ -1140,16 +1171,20 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
|
||||
} else {
|
||||
items.append(.custom(fontItem, false))
|
||||
//TODO:localize
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontSanFrancisco, icon: forceIsSerif ? emptyIcon : checkIcon, action: { (controller, action) in
|
||||
performAction.invoke(.updateFontIsSerif(false))
|
||||
action(.default)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_FontNewYork, textFont: .custom(font: Font.with(size: 17.0, design: .serif, traits: []), height: nil, verticalOffset: nil), icon: forceIsSerif ? checkIcon : emptyIcon, action: { (controller, action) in
|
||||
performAction.invoke(.updateFontIsSerif(true))
|
||||
action(.default)
|
||||
})))
|
||||
if case .webPage = contentState.contentType {
|
||||
let isAvailable = contentState.hasInstantView
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Instant View", textColor: isAvailable ? .primary : .disabled, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: isAvailable ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withAlphaComponent(0.3)) }, action: isAvailable ? { (controller, action) in
|
||||
performAction.invoke(.toggleInstantView(true))
|
||||
action(.default)
|
||||
} : nil)))
|
||||
} else if case .instantPage = contentState.contentType, contentState.isInnerInstantViewEnabled {
|
||||
items.append(.action(ContextMenuActionItem(text: "Hide Instant View", textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
|
||||
performAction.invoke(.toggleInstantView(false))
|
||||
action(.default)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
if !items.isEmpty {
|
||||
@ -1190,10 +1225,11 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
})))
|
||||
}
|
||||
}
|
||||
return ContextController.Items(content: .list(items))
|
||||
}
|
||||
|
||||
let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
|
||||
self.controller?.present(contextController, in: .window(.root))
|
||||
})
|
||||
let contextController = ContextController(presentationData: self.presentationData, source: source, items: items)
|
||||
self.controller?.present(contextController, in: .window(.root))
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
@ -1434,7 +1470,7 @@ public class BrowserScreen: ViewController, MinimizableController {
|
||||
|
||||
public enum Subject {
|
||||
case webPage(url: String)
|
||||
case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation)
|
||||
case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation, preloadedResources: [Any]?)
|
||||
case document(file: TelegramMediaFile, canShare: Bool)
|
||||
case pdfDocument(file: TelegramMediaFile, canShare: Bool)
|
||||
}
|
||||
|
@ -173,6 +173,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
private var presentationData: PresentationData
|
||||
|
||||
let webView: WebView
|
||||
var readability: Readability?
|
||||
|
||||
private let errorView: ComponentHostView<Empty>
|
||||
private var currentError: Error?
|
||||
@ -191,7 +192,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
|
||||
private let faviconDisposable = MetaDisposable()
|
||||
|
||||
var pushContent: (BrowserScreen.Subject) -> Void = { _ in }
|
||||
var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
|
||||
var openAppUrl: (String) -> Void = { _ in }
|
||||
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
|
||||
var minimize: () -> Void = { }
|
||||
@ -377,6 +378,29 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
self.webView.evaluateJavaScript(js) { _, _ in }
|
||||
}
|
||||
|
||||
func toggleInstantView(_ enabled: Bool) {
|
||||
if enabled {
|
||||
if let instantPage = self.instantPage {
|
||||
self.pushContent(.instantPage(webPage: instantPage, anchor: nil, sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel), preloadedResources: self.instantPageResources), self)
|
||||
} else if let readability = self.readability {
|
||||
readability.webView.frame = self.webView.frame
|
||||
self.addSubview(readability.webView)
|
||||
|
||||
var collapsedFrame = readability.webView.frame
|
||||
collapsedFrame.size.height = 0.0
|
||||
readability.webView.clipsToBounds = true
|
||||
readability.webView.layer.animateFrame(from: collapsedFrame, to: readability.webView.frame, duration: 0.3)
|
||||
}
|
||||
} else if let readability = self.readability {
|
||||
var collapsedFrame = readability.webView.frame
|
||||
collapsedFrame.size.height = 0.0
|
||||
readability.webView.layer.animateFrame(from: readability.webView.frame, to: collapsedFrame, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
readability.webView.removeFromSuperview()
|
||||
readability.webView.layer.removeAllAnimations()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private var didSetupSearch = false
|
||||
private func setupSearch(completion: @escaping () -> Void) {
|
||||
guard !self.didSetupSearch else {
|
||||
@ -764,6 +788,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
}
|
||||
}
|
||||
|
||||
private var instantPage: TelegramMediaWebpage?
|
||||
private var instantPageResources: [Any]?
|
||||
|
||||
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
||||
if let _ = self.currentError {
|
||||
self.currentError = nil
|
||||
@ -772,6 +799,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
}
|
||||
}
|
||||
self.updateFontState(self.currentFontState, force: true)
|
||||
|
||||
self.readability = nil
|
||||
self.instantPage = nil
|
||||
self.instantPageResources = nil
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
@ -780,6 +811,49 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
.withUpdatedForwardList(webView.backForwardList.forwardList.map { BrowserContentState.HistoryItem(webItem: $0) })
|
||||
}
|
||||
self.parseFavicon()
|
||||
|
||||
guard let url = URL(string: self._state.url) else {
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 14.5, *) {
|
||||
self.webView.createWebArchiveData { [weak self] result in
|
||||
guard let self, case let .success(data) = result else {
|
||||
return
|
||||
}
|
||||
let readability = Readability(url: url, archiveData: data, completionHandler: { [weak self] result, error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let (webPage, resources) = result {
|
||||
self.updateState {$0
|
||||
.withUpdatedHasInstantView(true)
|
||||
}
|
||||
self.instantPage = webPage
|
||||
self.instantPageResources = resources
|
||||
let _ = (updatedRemoteWebpage(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, webPage: WebpageReference(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: self._state.url, displayUrl: "", hash: 0, type: nil, websiteName: nil, title: nil, text: nil, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] webPage in
|
||||
guard let self, let webPage, case let .Loaded(result) = webPage.content, let _ = result.instantPage else {
|
||||
return
|
||||
}
|
||||
// let _ = self
|
||||
// #if DEBUG
|
||||
//
|
||||
// #else
|
||||
self.instantPage = webPage
|
||||
// #endif
|
||||
})
|
||||
} else {
|
||||
self.instantPage = nil
|
||||
self.instantPageResources = nil
|
||||
self.updateState {$0
|
||||
.withUpdatedHasInstantView(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
self.readability = readability
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
||||
@ -951,7 +1025,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
navigationController.pushViewController(controller)
|
||||
return (controller.node.content.last as? BrowserWebContent)?.webView
|
||||
} else {
|
||||
self.pushContent(subject)
|
||||
self.pushContent(subject, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ swift_library(
|
||||
"//submodules/LocationResources:LocationResources",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/TranslateUI:TranslateUI",
|
||||
"//submodules/Tuples:Tuples",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -27,7 +27,7 @@ public final class InstantPageAnchorItem: InstantPageItem {
|
||||
public func drawInTile(context: CGContext) {
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ public final class InstantPageArticleItem: InstantPageItem {
|
||||
self.hasRTL = hasRTL
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPageArticleNode(context: context, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ public final class InstantPageAudioItem: InstantPageItem {
|
||||
self.medias = [media]
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ public final class InstantPageContentNode : ASDisplayNode {
|
||||
private let openUrl: (InstantPageUrlItem) -> Void
|
||||
private let activatePinchPreview: ((PinchSourceContainerNode) -> Void)?
|
||||
private let pinchPreviewFinished: ((InstantPageNode) -> Void)?
|
||||
private let getPreloadedResource: (String) -> Data?
|
||||
|
||||
var currentLayoutTiles: [InstantPageTile] = []
|
||||
var currentLayoutItemsWithNodes: [InstantPageItem] = []
|
||||
@ -42,7 +43,7 @@ public final class InstantPageContentNode : ASDisplayNode {
|
||||
|
||||
private var previousVisibleBounds: CGRect?
|
||||
|
||||
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
|
||||
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, getPreloadedResource: @escaping (String) -> Data?) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -55,6 +56,7 @@ public final class InstantPageContentNode : ASDisplayNode {
|
||||
self.pinchPreviewFinished = pinchPreviewFinished
|
||||
self.openPeer = openPeer
|
||||
self.openUrl = openUrl
|
||||
self.getPreloadedResource = getPreloadedResource
|
||||
|
||||
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
||||
self.contentSize = contentSize
|
||||
@ -210,7 +212,7 @@ public final class InstantPageContentNode : ASDisplayNode {
|
||||
}, updateWebEmbedHeight: { _ in
|
||||
}, updateDetailsExpanded: { [weak self] expanded in
|
||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||
}, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: self.getPreloadedResource) {
|
||||
newNode.frame = itemFrame
|
||||
newNode.updateLayout(size: itemFrame.size, transition: transition)
|
||||
if let topNode = topNode {
|
||||
|
@ -649,7 +649,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
self?.updateWebEmbedHeight(embedIndex, height)
|
||||
}, updateDetailsExpanded: { [weak self] expanded in
|
||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||
}, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: { _ in return nil }) {
|
||||
newNode.frame = itemFrame
|
||||
newNode.updateLayout(size: itemFrame.size, transition: transition)
|
||||
if let topNode = topNode {
|
||||
|
@ -39,12 +39,12 @@ public final class InstantPageDetailsItem: InstantPageItem {
|
||||
self.index = index
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
var expanded: Bool?
|
||||
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
|
||||
expanded = currentlyExpanded
|
||||
}
|
||||
return InstantPageDetailsNode(context: context, sourceLocation: sourceLocation, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, item: self, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded)
|
||||
return InstantPageDetailsNode(context: context, sourceLocation: sourceLocation, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, item: self, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded, getPreloadedResource: getPreloadedResource)
|
||||
}
|
||||
|
||||
public func matchesAnchor(_ anchor: String) -> Bool {
|
||||
|
@ -35,7 +35,7 @@ public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
public var requestLayoutUpdate: ((Bool) -> Void)?
|
||||
|
||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
|
||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void, getPreloadedResource: @escaping (String) -> Data?) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -65,7 +65,7 @@ public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
||||
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded)
|
||||
self.separatorNode = ASDisplayNode()
|
||||
|
||||
self.contentNode = InstantPageContentNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, sourceLocation: sourceLocation, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, openPeer: openPeer, openUrl: openUrl)
|
||||
self.contentNode = InstantPageContentNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, sourceLocation: sourceLocation, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, openPeer: openPeer, openUrl: openUrl, getPreloadedResource: getPreloadedResource)
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import PersistentStringHash
|
||||
|
||||
public struct InstantPageExternalMediaResourceId {
|
||||
public let url: String
|
||||
|
||||
public var uniqueId: String {
|
||||
return "instantpage-media-\(persistentHash32(self.url))"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.uniqueId.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
public class InstantPageExternalMediaResource: TelegramMediaResource {
|
||||
public let url: String
|
||||
|
||||
public var size: Int64? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public init(url: String) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
public required init(decoder: PostboxDecoder) {
|
||||
self.url = decoder.decodeStringForKey("u", orElse: "")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.url, forKey: "u")
|
||||
}
|
||||
|
||||
public var id: MediaResourceId {
|
||||
return MediaResourceId(InstantPageExternalMediaResourceId(url: self.url).uniqueId)
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResource) -> Bool {
|
||||
if let to = to as? InstantPageExternalMediaResource {
|
||||
return self.url == to.url
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ public final class InstantPageFeedbackItem: InstantPageItem {
|
||||
self.webPage = webPage
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPageFeedbackNode(context: context, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,8 @@ public final class InstantPageImageItem: InstantPageItem {
|
||||
self.fit = fit
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
return InstantPageImageNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished)
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPageImageNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, getPreloadedResource: getPreloadedResource)
|
||||
}
|
||||
|
||||
public func matchesAnchor(_ anchor: String) -> Bool {
|
||||
|
@ -14,6 +14,7 @@ import LiveLocationPositionNode
|
||||
import AppBundle
|
||||
import TelegramUIPreferences
|
||||
import ContextUI
|
||||
import Tuples
|
||||
|
||||
private struct FetchControls {
|
||||
let fetch: (Bool) -> Void
|
||||
@ -48,7 +49,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
private var themeUpdated: Bool = false
|
||||
|
||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?) {
|
||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, getPreloadedResource: @escaping (String) -> Data?) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.webPage = webPage
|
||||
@ -72,51 +73,103 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
||||
self.addSubnode(self.pinchContainerNode)
|
||||
|
||||
if case let .image(image) = media.media, let largest = largestImageRepresentation(image.representations) {
|
||||
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
|
||||
self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference))
|
||||
|
||||
if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: image) {
|
||||
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start())
|
||||
}
|
||||
|
||||
self.fetchControls = FetchControls(fetch: { [weak self] manual in
|
||||
if let strongSelf = self {
|
||||
strongSelf.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start())
|
||||
if let externalResource = largest.resource as? InstantPageExternalMediaResource {
|
||||
var url = externalResource.url
|
||||
if !url.hasPrefix("http") && !url.hasPrefix("https") {
|
||||
url = "https:\(url)"
|
||||
}
|
||||
}, cancel: {
|
||||
chatMessagePhotoCancelInteractiveFetch(account: context.account, photoReference: imageReference)
|
||||
})
|
||||
|
||||
if interactive {
|
||||
self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
displayLinkDispatcher.dispatch {
|
||||
if let strongSelf = self {
|
||||
strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status)
|
||||
strongSelf.updateFetchStatus()
|
||||
let photoData: Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError>
|
||||
if let preloadedData = getPreloadedResource(externalResource.url) {
|
||||
photoData = .single(Tuple4(nil, preloadedData, .full, true))
|
||||
} else {
|
||||
photoData = context.engine.resources.httpData(url: url, preserveExactUrl: true)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Data?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { data in
|
||||
if let data {
|
||||
return Tuple4(nil, data, .full, true)
|
||||
} else {
|
||||
return Tuple4(nil, nil, .full, false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: photoData)
|
||||
|> map { _, _, generate in
|
||||
return generate
|
||||
})
|
||||
} else {
|
||||
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
|
||||
self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference))
|
||||
|
||||
if media.url != nil {
|
||||
self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink")
|
||||
self.pinchContainerNode.contentNode.addSubnode(self.linkIconNode)
|
||||
if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: image) {
|
||||
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start())
|
||||
}
|
||||
|
||||
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||
self.fetchControls = FetchControls(fetch: { [weak self] manual in
|
||||
if let strongSelf = self {
|
||||
strongSelf.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start())
|
||||
}
|
||||
}, cancel: {
|
||||
chatMessagePhotoCancelInteractiveFetch(account: context.account, photoReference: imageReference)
|
||||
})
|
||||
|
||||
if interactive {
|
||||
self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
displayLinkDispatcher.dispatch {
|
||||
if let strongSelf = self {
|
||||
strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status)
|
||||
strongSelf.updateFetchStatus()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
if media.url != nil {
|
||||
self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink")
|
||||
self.pinchContainerNode.contentNode.addSubnode(self.linkIconNode)
|
||||
}
|
||||
|
||||
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||
}
|
||||
}
|
||||
} else if case let .file(file) = media.media {
|
||||
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
|
||||
if file.mimeType.hasPrefix("image/") {
|
||||
if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) {
|
||||
_ = freeMediaFileInteractiveFetched(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference).start()
|
||||
if let externalResource = file.resource as? InstantPageExternalMediaResource {
|
||||
let photoData: Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError>
|
||||
if let preloadedData = getPreloadedResource(externalResource.url) {
|
||||
photoData = .single(Tuple4(nil, preloadedData, .full, true))
|
||||
} else {
|
||||
photoData = context.engine.resources.httpData(url: externalResource.url, preserveExactUrl: true)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Data?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { data in
|
||||
if let data {
|
||||
return Tuple4(nil, data, .full, true)
|
||||
} else {
|
||||
return Tuple4(nil, nil, .full, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.imageNode.setSignal(instantPageImageFile(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference, fetched: true))
|
||||
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: photoData)
|
||||
|> map { _, _, generate in
|
||||
return generate
|
||||
})
|
||||
} else {
|
||||
self.imageNode.setSignal(chatMessageVideo(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, videoReference: fileReference))
|
||||
}
|
||||
if file.isVideo {
|
||||
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
|
||||
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
|
||||
if file.mimeType.hasPrefix("image/") {
|
||||
if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) {
|
||||
_ = freeMediaFileInteractiveFetched(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference).start()
|
||||
}
|
||||
self.imageNode.setSignal(instantPageImageFile(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference, fetched: true))
|
||||
} else {
|
||||
self.imageNode.setSignal(chatMessageVideo(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, videoReference: fileReference))
|
||||
}
|
||||
if file.isVideo {
|
||||
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
|
||||
self.pinchContainerNode.contentNode.addSubnode(self.statusNode)
|
||||
}
|
||||
}
|
||||
} else if case let .geo(map) = media.media {
|
||||
self.addSubnode(self.pinNode)
|
||||
|
@ -15,7 +15,7 @@ public protocol InstantPageItem {
|
||||
|
||||
func matchesAnchor(_ anchor: String) -> Bool
|
||||
func drawInTile(context: CGContext)
|
||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode?
|
||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode?
|
||||
func matchesNode(_ node: InstantPageNode) -> Bool
|
||||
func linkSelectionRects(at point: CGPoint) -> [CGRect]
|
||||
|
||||
|
@ -884,9 +884,11 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, userLoc
|
||||
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
|
||||
contentSize.height += closingSpacing
|
||||
|
||||
let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0), webPage: webPage)
|
||||
contentSize.height += feedbackItem.frame.height
|
||||
items.append(feedbackItem)
|
||||
if webPage.webpageId.id != 0 {
|
||||
let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0), webPage: webPage)
|
||||
contentSize.height += feedbackItem.frame.height
|
||||
items.append(feedbackItem)
|
||||
}
|
||||
|
||||
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public final class InstantPagePeerReferenceItem: InstantPageItem {
|
||||
self.rtl = rtl
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPagePeerReferenceNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer)
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ public final class InstantPagePlayableVideoItem: InstantPageItem {
|
||||
self.interactive = interactive
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPagePlayableVideoNode(context: context, userLocation: sourceLocation.userLocation, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia)
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let (_, items, contentSize) = layoutTextItemWithString(self.anchorText, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
|
||||
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, sourceLocation: self.sourceLocation, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in })
|
||||
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, sourceLocation: self.sourceLocation, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in }, getPreloadedResource: { url in return nil})
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
|
||||
self.contentContainerNode.insertSubnode(contentNode, at: 0)
|
||||
self.contentNode = contentNode
|
||||
|
@ -61,7 +61,7 @@ public final class InstantPageShapeItem: InstantPageItem {
|
||||
return false
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
|
||||
self.medias = medias
|
||||
}
|
||||
|
||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPageSlideshowNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished)
|
||||
}
|
||||
|
||||
|
@ -187,7 +187,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, ASScrollViewDe
|
||||
let media = self.items[index]
|
||||
let contentNode: ASDisplayNode
|
||||
if case .image = media.media {
|
||||
contentNode = InstantPageImageNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: self.activatePinchPreview, pinchPreviewFinished: self.pinchPreviewFinished)
|
||||
contentNode = InstantPageImageNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: self.activatePinchPreview, pinchPreviewFinished: self.pinchPreviewFinished, getPreloadedResource: { _ in return nil })
|
||||
} else if case .file = media.media {
|
||||
contentNode = ASDisplayNode()
|
||||
} else {
|
||||
|
@ -198,7 +198,7 @@ final class InstantPageSubContentNode : ASDisplayNode {
|
||||
}, updateWebEmbedHeight: { _ in
|
||||
}, updateDetailsExpanded: { [weak self] expanded in
|
||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||
}, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: { _ in return nil }) {
|
||||
newNode.frame = itemFrame
|
||||
newNode.updateLayout(size: itemFrame.size, transition: transition)
|
||||
if let topNode = topNode {
|
||||
|
@ -199,12 +199,12 @@ public final class InstantPageTableItem: InstantPageScrollableItem {
|
||||
return false
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
var additionalNodes: [InstantPageNode] = []
|
||||
for cell in self.cells {
|
||||
for item in cell.additionalItems {
|
||||
if item.wantsNode {
|
||||
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourceLocation: sourceLocation, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
|
||||
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourceLocation: sourceLocation, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil, getPreloadedResource: getPreloadedResource) {
|
||||
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
|
||||
additionalNodes.append(node)
|
||||
}
|
||||
|
@ -435,7 +435,7 @@ public final class InstantPageTextItem: InstantPageItem {
|
||||
return false
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -484,11 +484,11 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
var additionalNodes: [InstantPageNode] = []
|
||||
for item in additionalItems {
|
||||
if item.wantsNode {
|
||||
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourceLocation: sourceLocation, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
|
||||
if let node = item.node(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, sourceLocation: sourceLocation, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil, getPreloadedResource: getPreloadedResource) {
|
||||
node.frame = item.frame
|
||||
additionalNodes.append(node)
|
||||
}
|
||||
|
@ -100,12 +100,12 @@ final class InstantPageTextStyleStack {
|
||||
}
|
||||
case .subscript:
|
||||
if baselineOffset == nil {
|
||||
baselineOffset = 0.35
|
||||
baselineOffset = -0.35
|
||||
underline = false
|
||||
}
|
||||
case .superscript:
|
||||
if baselineOffset == nil {
|
||||
baselineOffset = -0.35
|
||||
baselineOffset = 0.35
|
||||
}
|
||||
case let .markerColor(color):
|
||||
if markerColor == nil {
|
||||
|
@ -24,7 +24,7 @@ public final class InstantPageWebEmbedItem: InstantPageItem {
|
||||
self.enableScrolling = enableScrolling
|
||||
}
|
||||
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||
public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? {
|
||||
return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
|
||||
}
|
||||
|
||||
|
@ -81,11 +81,21 @@ public func presentGiveawayInfoController(
|
||||
onlyNewSubscribers = true
|
||||
}
|
||||
|
||||
let author = message.forwardInfo?.author ?? message.author?._asPeer()
|
||||
var author = message.forwardInfo?.author ?? message.author?._asPeer()
|
||||
if author is TelegramChannel {
|
||||
} else {
|
||||
if let peer = message.forwardInfo?.source ?? message.peers[message.id.peerId] {
|
||||
author = peer
|
||||
}
|
||||
}
|
||||
var isGroup = false
|
||||
if let channel = author as? TelegramChannel, case .group = channel.info {
|
||||
isGroup = true
|
||||
}
|
||||
var peerName = ""
|
||||
if let author {
|
||||
peerName = EnginePeer(author).compactDisplayTitle
|
||||
}
|
||||
|
||||
var groupsAndChannels = false
|
||||
var channelsCount: Int32 = 1
|
||||
@ -113,10 +123,7 @@ public func presentGiveawayInfoController(
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var peerName = ""
|
||||
if let channel = author as? TelegramChannel {
|
||||
peerName = EnginePeer(channel).compactDisplayTitle
|
||||
}
|
||||
|
||||
|
||||
let timeZone = TimeZone.current
|
||||
let untilDate = stringForDate(timestamp: untilDateValue, timeZone: timeZone, strings: presentationData.strings)
|
||||
|
@ -3,8 +3,12 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import MtProtoKit
|
||||
|
||||
public func fetchHttpResource(url: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||
if let urlString = url.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed), let url = URL(string: urlString) {
|
||||
public func fetchHttpResource(url: String, preserveExactUrl: Bool = false) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||
var urlString: String? = url
|
||||
if !preserveExactUrl {
|
||||
urlString = url.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
|
||||
}
|
||||
if let urlString, let url = URL(string: urlString) {
|
||||
let signal = MTHttpRequestOperation.data(forHttpUrl: url)!
|
||||
return Signal { subscriber in
|
||||
subscriber.putNext(.reset)
|
||||
|
@ -341,8 +341,8 @@ public extension TelegramEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public func httpData(url: String) -> Signal<Data, EngineMediaResource.Fetch.Error> {
|
||||
return fetchHttpResource(url: url)
|
||||
public func httpData(url: String, preserveExactUrl: Bool = false) -> Signal<Data, EngineMediaResource.Fetch.Error> {
|
||||
return fetchHttpResource(url: url, preserveExactUrl: preserveExactUrl)
|
||||
|> mapError { _ -> EngineMediaResource.Fetch.Error in
|
||||
return .generic
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ public func actualizedWebpage(account: Account, webpage: TelegramMediaWebpage) -
|
||||
}
|
||||
}
|
||||
|
||||
func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id, webPage: WebpageReference) -> Signal<TelegramMediaWebpage?, NoError> {
|
||||
public func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id, webPage: WebpageReference) -> Signal<TelegramMediaWebpage?, NoError> {
|
||||
if case let .webPage(id, url) = webPage.content {
|
||||
return network.request(Api.functions.messages.getWebPage(url: url, hash: 0))
|
||||
|> map(Optional.init)
|
||||
@ -270,14 +270,20 @@ func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: Eng
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
|
||||
if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId.id == id {
|
||||
return postbox.transaction { transaction -> TelegramMediaWebpage? in
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||
if transaction.getMedia(updatedWebpage.webpageId) != nil {
|
||||
updateMessageMedia(transaction: transaction, id: updatedWebpage.webpageId, media: updatedWebpage)
|
||||
if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage), case .Loaded = updatedWebpage.content {
|
||||
if updatedWebpage.webpageId.id == id {
|
||||
return postbox.transaction { transaction -> TelegramMediaWebpage? in
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||
if transaction.getMedia(updatedWebpage.webpageId) != nil {
|
||||
updateMessageMedia(transaction: transaction, id: updatedWebpage.webpageId, media: updatedWebpage)
|
||||
}
|
||||
return updatedWebpage
|
||||
}
|
||||
return updatedWebpage
|
||||
} else if id == 0 {
|
||||
return .single(updatedWebpage)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
|
@ -79,6 +79,11 @@ private final class NavigationContainer: UIView, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
public final class NavigationStackComponent<ChildEnvironment: Equatable>: Component {
|
||||
public enum CurlTransition {
|
||||
case show
|
||||
case hide
|
||||
}
|
||||
|
||||
public let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||
public let requestPop: () -> Void
|
||||
|
||||
@ -105,7 +110,7 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
|
||||
super.init(frame: frame)
|
||||
|
||||
self.dimView.alpha = 0.0
|
||||
self.dimView.backgroundColor = UIColor.black.withAlphaComponent(0.2)
|
||||
self.dimView.backgroundColor = UIColor.black.withAlphaComponent(0.3)
|
||||
self.dimView.isUserInteractionEnabled = false
|
||||
self.addSubview(self.dimView)
|
||||
}
|
||||
@ -166,11 +171,20 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var transition = transition
|
||||
var curlTransition: NavigationStackComponent<ChildEnvironment>.CurlTransition?
|
||||
if let curlTransitionValue = transition.userData(NavigationStackComponent<ChildEnvironment>.CurlTransition.self) {
|
||||
transition = .immediate
|
||||
curlTransition = curlTransitionValue
|
||||
}
|
||||
|
||||
let navigationTransitionFraction = self.navigationContainer.transitionFraction
|
||||
self.navigationContainer.isNavigationEnabled = component.items.count > 1
|
||||
|
||||
var validItemIds: [AnyHashable] = []
|
||||
|
||||
var removeImpl: (() -> Void)?
|
||||
|
||||
var readyItems: [ReadyItem] = []
|
||||
for i in 0 ..< component.items.count {
|
||||
let item = component.items[i]
|
||||
@ -184,6 +198,7 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
|
||||
} else {
|
||||
itemTransition = itemTransition.withAnimation(.none)
|
||||
itemView = ItemView()
|
||||
itemView.clipsToBounds = true
|
||||
self.itemViews[itemId] = itemView
|
||||
itemView.contents.parentState = state
|
||||
}
|
||||
@ -242,7 +257,18 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
|
||||
readyItem.itemTransition.setFrame(view: readyItem.itemView.dimView, frame: CGRect(origin: .zero, size: availableSize))
|
||||
readyItem.itemTransition.setAlpha(view: readyItem.itemView.dimView, alpha: 1.0 - alphaTransitionFraction)
|
||||
|
||||
if readyItem.index > 0 && isAdded {
|
||||
if curlTransition == .show && isAdded {
|
||||
var fromFrame = itemFrame
|
||||
fromFrame.size.height = 0.0
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.3)
|
||||
transition.animateBoundsSize(view: readyItem.itemView, from: fromFrame.size, to: itemFrame.size, completion: { _ in
|
||||
removeImpl?()
|
||||
})
|
||||
transition.animatePosition(view: readyItem.itemView, from: fromFrame.center, to: itemFrame.center)
|
||||
} else if curlTransition == .hide && isAdded {
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.3)
|
||||
transition.animateAlpha(view: readyItem.itemView.dimView, from: 1.0, to: 0.0)
|
||||
} else if readyItem.index > 0 && isAdded {
|
||||
transition.animatePosition(view: itemComponentView, from: CGPoint(x: itemFrame.width, y: 0.0), to: .zero, additive: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@ -263,23 +289,49 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
|
||||
removedItemIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removedItemIds {
|
||||
guard let itemView = self.itemViews[id] else {
|
||||
continue
|
||||
}
|
||||
if let itemComponeentView = itemView.contents.view {
|
||||
var position = itemComponeentView.center
|
||||
position.x += itemComponeentView.bounds.width
|
||||
transition.setPosition(view: itemComponeentView, position: position, completion: { _ in
|
||||
|
||||
removeImpl = {
|
||||
for id in removedItemIds {
|
||||
guard let itemView = self.itemViews[id] else {
|
||||
continue
|
||||
}
|
||||
if let itemComponentView = itemView.contents.view, curlTransition != .show {
|
||||
if curlTransition == .hide {
|
||||
itemView.superview?.bringSubviewToFront(itemView)
|
||||
var toFrame = itemView.frame
|
||||
toFrame.size.height = 0.0
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.3)
|
||||
transition.setFrame(view: itemView, frame: toFrame, completion: { _ in
|
||||
itemView.removeFromSuperview()
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
})
|
||||
} else {
|
||||
var position = itemComponentView.center
|
||||
position.x += itemComponentView.bounds.width
|
||||
transition.setPosition(view: itemComponentView, position: position, completion: { _ in
|
||||
itemView.removeFromSuperview()
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
itemView.removeFromSuperview()
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
})
|
||||
} else {
|
||||
itemView.removeFromSuperview()
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if curlTransition == .show {
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.3)
|
||||
for id in removedItemIds {
|
||||
guard let itemView = self.itemViews[id] else {
|
||||
continue
|
||||
}
|
||||
transition.setAlpha(view: itemView.dimView, alpha: 1.0)
|
||||
}
|
||||
} else {
|
||||
removeImpl?()
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||
self.navigationContainer.frame = CGRect(origin: .zero, size: contentSize)
|
||||
|
||||
|
2754
submodules/TelegramUI/Resources/Readability/Readability.js
Normal file
2754
submodules/TelegramUI/Resources/Readability/Readability.js
Normal file
File diff suppressed because it is too large
Load Diff
50
submodules/TelegramUI/Resources/Readability/ReaderMode.js
Normal file
50
submodules/TelegramUI/Resources/Readability/ReaderMode.js
Normal file
File diff suppressed because one or more lines are too long
3
submodules/TelegramUI/Resources/Readability/purify.min.js
vendored
Normal file
3
submodules/TelegramUI/Resources/Readability/purify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -402,7 +402,7 @@ func makeInstantPageControllerImpl(context: AccountContext, message: Message, so
|
||||
}
|
||||
|
||||
func makeInstantPageControllerImpl(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController {
|
||||
return BrowserScreen(context: context, subject: .instantPage(webPage: webPage, anchor: anchor, sourceLocation: sourceLocation))
|
||||
return BrowserScreen(context: context, subject: .instantPage(webPage: webPage, anchor: anchor, sourceLocation: sourceLocation, preloadedResources: nil))
|
||||
}
|
||||
|
||||
func openChatWallpaperImpl(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user