Local instant view

This commit is contained in:
Ilya Laktyushin 2024-08-29 11:44:05 +04:00
parent 0d1366e5cc
commit 5978a5278d
41 changed files with 3967 additions and 164 deletions

View File

@ -41,6 +41,8 @@ final class BrowserContentState: Equatable {
let contentType: ContentType let contentType: ContentType
let favicon: UIImage? let favicon: UIImage?
let isSecure: Bool let isSecure: Bool
let hasInstantView: Bool
let isInnerInstantViewEnabled: Bool
let canGoBack: Bool let canGoBack: Bool
let canGoForward: Bool let canGoForward: Bool
@ -56,6 +58,8 @@ final class BrowserContentState: Equatable {
contentType: ContentType, contentType: ContentType,
favicon: UIImage? = nil, favicon: UIImage? = nil,
isSecure: Bool = false, isSecure: Bool = false,
hasInstantView: Bool = false,
isInnerInstantViewEnabled: Bool = false,
canGoBack: Bool = false, canGoBack: Bool = false,
canGoForward: Bool = false, canGoForward: Bool = false,
backList: [HistoryItem] = [], backList: [HistoryItem] = [],
@ -68,6 +72,8 @@ final class BrowserContentState: Equatable {
self.contentType = contentType self.contentType = contentType
self.favicon = favicon self.favicon = favicon
self.isSecure = isSecure self.isSecure = isSecure
self.hasInstantView = hasInstantView
self.isInnerInstantViewEnabled = isInnerInstantViewEnabled
self.canGoBack = canGoBack self.canGoBack = canGoBack
self.canGoForward = canGoForward self.canGoForward = canGoForward
self.backList = backList self.backList = backList
@ -96,6 +102,9 @@ final class BrowserContentState: Equatable {
if lhs.isSecure != rhs.isSecure { if lhs.isSecure != rhs.isSecure {
return false return false
} }
if lhs.hasInstantView != rhs.hasInstantView {
return false
}
if lhs.canGoBack != rhs.canGoBack { if lhs.canGoBack != rhs.canGoBack {
return false return false
} }
@ -112,43 +121,51 @@ final class BrowserContentState: Equatable {
} }
func withUpdatedTitle(_ title: String) -> BrowserContentState { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 currentState: BrowserContentState { get }
var state: Signal<BrowserContentState, NoError> { 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 present: (ViewController, Any?) -> Void { get set }
var presentInGlobalOverlay: (ViewController) -> Void { get set } var presentInGlobalOverlay: (ViewController) -> Void { get set }
var getNavigationController: () -> NavigationController? { get set } var getNavigationController: () -> NavigationController? { get set }
@ -177,6 +194,8 @@ protocol BrowserContent: UIView {
func navigateForward() func navigateForward()
func navigateTo(historyItem: BrowserContentState.HistoryItem) func navigateTo(historyItem: BrowserContentState.HistoryItem)
func toggleInstantView(_ enabled: Bool)
func updatePresentationData(_ presentationData: PresentationData) func updatePresentationData(_ presentationData: PresentationData)
func updateFontState(_ state: BrowserPresentationState.FontState) func updateFontState(_ state: BrowserPresentationState.FontState)

View File

@ -36,7 +36,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
return self.statePromise.get() return self.statePromise.get()
} }
var pushContent: (BrowserScreen.Subject) -> Void = { _ in } var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
var openAppUrl: (String) -> Void = { _ in } var openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { } var minimize: () -> Void = { }
@ -122,6 +122,9 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
self.webView.evaluateJavaScript(js) { _, _ in } self.webView.evaluateJavaScript(js) { _, _ in }
} }
func toggleInstantView(_ enabled: Bool) {
}
private var didSetupSearch = false private var didSetupSearch = false
private func setupSearch(completion: @escaping () -> Void) { private func setupSearch(completion: @escaping () -> Void) {
guard !self.didSetupSearch else { guard !self.didSetupSearch else {
@ -385,7 +388,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
navigationController._keepModalDismissProgress = true navigationController._keepModalDismissProgress = true
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
} else { } else {
self.pushContent(subject) self.pushContent(subject, nil)
} }
} }

View File

@ -28,6 +28,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
private var theme: InstantPageTheme private var theme: InstantPageTheme
private var settings: InstantPagePresentationSettings = .defaultSettings private var settings: InstantPagePresentationSettings = .defaultSettings
private let sourceLocation: InstantPageSourceLocation private let sourceLocation: InstantPageSourceLocation
private let preloadedResouces: [Any]?
private var originalContent: BrowserContent?
private var webPage: TelegramMediaWebpage? private var webPage: TelegramMediaWebpage?
@ -66,7 +68,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
var currentAccessibilityAreas: [AccessibilityAreaNode] = [] 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 openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { } var minimize: () -> Void = { }
@ -84,19 +87,21 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
private let loadWebpageDisposable = MetaDisposable() private let loadWebpageDisposable = MetaDisposable()
private let resolveUrlDisposable = MetaDisposable() private let resolveUrlDisposable = MetaDisposable()
private let updateLayoutDisposable = MetaDisposable() private let updateLayoutDisposable = MetaDisposable()
private let loadProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true) private let loadProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true)
private let readingProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true) private let readingProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true)
private var containerLayout: (size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets)? private var containerLayout: (size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets)?
private var setupScrollOffsetOnLayout = false 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.context = context
self.webPage = webPage self.webPage = webPage
self.presentationData = presentationData self.presentationData = presentationData
self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: .defaultSettings) self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: .defaultSettings)
self.sourceLocation = sourceLocation self.sourceLocation = sourceLocation
self.preloadedResouces = preloadedResouces
self.originalContent = originalContent
self.uuid = UUID() self.uuid = UUID()
@ -107,7 +112,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
title = "" 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.statePromise = Promise<BrowserContentState>(self._state)
self.wrapperNode = ASDisplayNode() self.wrapperNode = ASDisplayNode()
@ -126,7 +132,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.readingProgress.get() self.readingProgress.get()
) )
|> map { estimatedProgress, readingProgress in |> 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)
} }
)) ))
@ -359,6 +365,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.updatePageLayout() self.updatePageLayout()
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) 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)?) { func setSearch(_ query: String?, completion: ((Int) -> Void)?) {
@ -603,7 +615,22 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self?.updateWebEmbedHeight(embedIndex, height) self?.updateWebEmbedHeight(embedIndex, height)
}, updateDetailsExpanded: { [weak self] expanded in }, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded) 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.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition) newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode { if let topNode = topNode {
@ -878,7 +905,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
baseUrl = String(baseUrl[..<anchorRange.lowerBound]) 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) self.scrollToAnchor(anchor)
return return
} }
@ -905,7 +932,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
case let .result(webpageResult): case let .result(webpageResult):
if let webpageResult = webpageResult, case .Loaded = webpageResult.webpage.content { if let webpageResult = webpageResult, case .Loaded = webpageResult.webpage.content {
strongSelf.loadProgress.set(1.0) 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 break
case let .progress(progress): case let .progress(progress):
@ -915,11 +942,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
})) }))
} else { } else {
strongSelf.loadProgress.set(1.0) strongSelf.loadProgress.set(1.0)
strongSelf.pushContent(.webPage(url: externalUrl)) strongSelf.pushContent(.webPage(url: externalUrl), nil)
} }
case let .instantView(webpage, anchor): case let .instantView(webpage, anchor):
strongSelf.loadProgress.set(1.0) 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: default:
strongSelf.loadProgress.set(1.0) strongSelf.loadProgress.set(1.0)
strongSelf.minimize() strongSelf.minimize()

View File

@ -24,7 +24,12 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
private let pdfView: PDFView private let pdfView: PDFView
private let scrollView: UIScrollView! 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 let uuid: UUID
private var _state: BrowserContentState private var _state: BrowserContentState
@ -37,7 +42,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
return self.statePromise.get() return self.statePromise.get()
} }
var pushContent: (BrowserScreen.Subject) -> Void = { _ in } var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
var openAppUrl: (String) -> Void = { _ in } var openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { } var minimize: () -> Void = { }
@ -57,6 +62,10 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
self.pdfView = PDFView() self.pdfView = PDFView()
self.pdfView.clipsToBounds = false self.pdfView.clipsToBounds = false
self.pageIndicatorBackgorund = UIVisualEffectView(effect: UIBlurEffect(style: .light))
self.pageIndicatorBackgorund.clipsToBounds = true
self.pageIndicatorBackgorund.layer.cornerRadius = 10.0
var scrollView: UIScrollView? var scrollView: UIScrollView?
for view in self.pdfView.subviews { for view in self.pdfView.subviews {
if let view = view as? UIScrollView { if let view = view as? UIScrollView {
@ -104,6 +113,10 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
scrollView.delegate = self scrollView.delegate = self
} }
} }
self.pageNumber = (1, self.pdfView.document?.pageCount ?? 1)
self.startPageIndicatorTimer()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -120,12 +133,27 @@ 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) { func updateFontState(_ state: BrowserPresentationState.FontState) {
} }
func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) { func updateFontState(_ state: BrowserPresentationState.FontState, force: Bool) {
} }
func toggleInstantView(_ enabled: Bool) {
}
private var findSession: Any? private var findSession: Any?
private var previousQuery: String? private var previousQuery: String?
@ -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)) 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) 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 { if isFirstTime {
self.pdfView.setNeedsLayout() self.pdfView.setNeedsLayout()
self.pdfView.layoutIfNeeded() self.pdfView.layoutIfNeeded()
@ -370,9 +421,31 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
} }
if !scrollView.isZooming && !self.wasZooming { if !scrollView.isZooming && !self.wasZooming {
self.updateScrollingOffset(isReset: false, transition: .immediate) 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) { public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if let scrollViewDelegate = scrollView as? UIScrollViewDelegate { if let scrollViewDelegate = scrollView as? UIScrollViewDelegate {
scrollViewDelegate.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) scrollViewDelegate.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
@ -383,6 +456,8 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
if self.ignoreUpdatesUntilScrollingStopped { if self.ignoreUpdatesUntilScrollingStopped {
self.ignoreUpdatesUntilScrollingStopped = false self.ignoreUpdatesUntilScrollingStopped = false
} }
self.startPageIndicatorTimer()
} }
} }
@ -395,6 +470,8 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
if self.ignoreUpdatesUntilScrollingStopped { if self.ignoreUpdatesUntilScrollingStopped {
self.ignoreUpdatesUntilScrollingStopped = false self.ignoreUpdatesUntilScrollingStopped = false
} }
self.startPageIndicatorTimer()
} }
private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) { private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) {
@ -449,7 +526,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF
navigationController._keepModalDismissProgress = true navigationController._keepModalDismissProgress = true
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
} else { } else {
self.pushContent(subject) self.pushContent(subject, nil)
} }
} }

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

View File

@ -492,6 +492,7 @@ public class BrowserScreen: ViewController, MinimizableController {
case increaseFontSize case increaseFontSize
case resetFontSize case resetFontSize
case updateFontIsSerif(Bool) case updateFontIsSerif(Bool)
case toggleInstantView(Bool)
case addBookmark case addBookmark
case openBookmarks case openBookmarks
case openAddressBar case openAddressBar
@ -519,7 +520,7 @@ public class BrowserScreen: ViewController, MinimizableController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
init(controller: BrowserScreen) { init(controller: BrowserScreen) {
self.context = controller.context self.context = controller.context
self.controller = controller self.controller = controller
@ -755,6 +756,8 @@ public class BrowserScreen: ViewController, MinimizableController {
return updatedState return updatedState
}) })
content.updateFontState(self.presentationState.fontState) content.updateFontState(self.presentationState.fontState)
case let .toggleInstantView(enabled):
content.toggleInstantView(enabled)
case .addBookmark: case .addBookmark:
if let content = self.content.last { if let content = self.content.last {
self.addBookmark(content.currentState.url, showArrow: true) self.addBookmark(content.currentState.url, showArrow: true)
@ -822,7 +825,7 @@ public class BrowserScreen: ViewController, MinimizableController {
self.requestLayout(transition: transition) self.requestLayout(transition: transition)
} }
func pushContent(_ content: BrowserScreen.Subject, transition: ComponentTransition) { func pushContent(_ content: BrowserScreen.Subject, additionalContent: BrowserContent? = nil, transition: ComponentTransition) {
let browserContent: BrowserContent let browserContent: BrowserContent
switch content { switch content {
case let .webPage(url): case let .webPage(url):
@ -834,25 +837,37 @@ public class BrowserScreen: ViewController, MinimizableController {
} }
browserContent = webContent browserContent = webContent
self.controller?.preferredConfiguration = nil self.controller?.preferredConfiguration = nil
case let .instantPage(webPage, anchor, 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) 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 instantPageContent.openPeer = { [weak self] peer in
guard let self else { guard let self else {
return return
} }
self.openPeer(peer) 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 browserContent = instantPageContent
case let .document(file, _): case let .document(file, _):
browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file) browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file)
case let .pdfDocument(file, _): case let .pdfDocument(file, _):
browserContent = BrowserPdfContent(context: self.context, presentationData: self.presentationData, file: 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 { guard let self else {
return 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 browserContent.openAppUrl = { [weak self] url in
guard let self else { 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.requestLayout(transition: transition)
self.setupContentStateUpdates() self.setupContentStateUpdates()
@ -1050,16 +1073,20 @@ public class BrowserScreen: ViewController, MinimizableController {
return return
} }
guard let controller = self.controller, let content = self.content.last else {
return
}
if let animationComponentView = referenceView.componentView.view as? LottieComponent.View { if let animationComponentView = referenceView.componentView.view as? LottieComponent.View {
animationComponentView.playOnce() animationComponentView.playOnce()
} }
self.view.endEditing(true) self.view.endEditing(true)
let checkIcon: (PresentationTheme) -> UIImage? = { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Check"), color: theme.contextMenu.primaryColor) } // let checkIcon: (PresentationTheme) -> UIImage? = { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Check"), color: theme.contextMenu.primaryColor) }
let emptyIcon: (PresentationTheme) -> UIImage? = { _ in // let emptyIcon: (PresentationTheme) -> UIImage? = { _ in
return nil // return nil
} // }
let settings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]) let settings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings])
|> take(1) |> take(1)
@ -1071,17 +1098,21 @@ public class BrowserScreen: ViewController, MinimizableController {
} }
} }
let _ = (settings let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view))
|> deliverOnMainQueue).start(next: { [weak self] settings in
guard let self, let controller = self.controller, let contentState = self.contentState, let layout = self.validLayout?.0 else { let items: Signal<ContextController.Items, NoError> = combineLatest(
return 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 source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view))
let performAction = self.performAction let performAction = self.performAction
let forceIsSerif = self.presentationState.fontState.isSerif // let forceIsSerif = self.presentationState.fontState.isSerif
let fontItem = BrowserFontSizeContextMenuItem( let fontItem = BrowserFontSizeContextMenuItem(
value: self.presentationState.fontState.size, value: self.presentationState.fontState.size,
decrease: { [weak self] in decrease: { [weak self] in
@ -1140,16 +1171,20 @@ public class BrowserScreen: ViewController, MinimizableController {
} else { } else {
items.append(.custom(fontItem, false)) 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 if case .webPage = contentState.contentType {
performAction.invoke(.updateFontIsSerif(false)) let isAvailable = contentState.hasInstantView
action(.default) 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)
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 } : nil)))
performAction.invoke(.updateFontIsSerif(true)) } else if case .instantPage = contentState.contentType, contentState.isInnerInstantViewEnabled {
action(.default) 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 { 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? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -1434,7 +1470,7 @@ public class BrowserScreen: ViewController, MinimizableController {
public enum Subject { public enum Subject {
case webPage(url: String) 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 document(file: TelegramMediaFile, canShare: Bool)
case pdfDocument(file: TelegramMediaFile, canShare: Bool) case pdfDocument(file: TelegramMediaFile, canShare: Bool)
} }

View File

@ -173,6 +173,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
private var presentationData: PresentationData private var presentationData: PresentationData
let webView: WebView let webView: WebView
var readability: Readability?
private let errorView: ComponentHostView<Empty> private let errorView: ComponentHostView<Empty>
private var currentError: Error? private var currentError: Error?
@ -191,7 +192,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
private let faviconDisposable = MetaDisposable() private let faviconDisposable = MetaDisposable()
var pushContent: (BrowserScreen.Subject) -> Void = { _ in } var pushContent: (BrowserScreen.Subject, BrowserContent?) -> Void = { _, _ in }
var openAppUrl: (String) -> Void = { _ in } var openAppUrl: (String) -> Void = { _ in }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
var minimize: () -> Void = { } var minimize: () -> Void = { }
@ -238,7 +239,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
contentController.add(WeakScriptMessageHandler { message in contentController.add(WeakScriptMessageHandler { message in
handleScriptMessageImpl?(message) handleScriptMessageImpl?(message)
}, name: "performAction") }, name: "performAction")
configuration.userContentController = contentController configuration.userContentController = contentController
configuration.applicationNameForUserAgent = computedUserAgent() configuration.applicationNameForUserAgent = computedUserAgent()
} }
@ -377,13 +378,36 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.webView.evaluateJavaScript(js) { _, _ in } 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 var didSetupSearch = false
private func setupSearch(completion: @escaping () -> Void) { private func setupSearch(completion: @escaping () -> Void) {
guard !self.didSetupSearch else { guard !self.didSetupSearch else {
completion() completion()
return return
} }
let bundle = getAppBundle() let bundle = getAppBundle()
guard let scriptPath = bundle.path(forResource: "UIWebViewSearch", ofType: "js") else { guard let scriptPath = bundle.path(forResource: "UIWebViewSearch", ofType: "js") else {
return return
@ -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!) { func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
if let _ = self.currentError { if let _ = self.currentError {
self.currentError = nil self.currentError = nil
@ -772,6 +799,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
} }
} }
self.updateFontState(self.currentFontState, force: true) self.updateFontState(self.currentFontState, force: true)
self.readability = nil
self.instantPage = nil
self.instantPageResources = nil
} }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 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) }) .withUpdatedForwardList(webView.backForwardList.forwardList.map { BrowserContentState.HistoryItem(webItem: $0) })
} }
self.parseFavicon() 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) { func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
@ -951,7 +1025,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
return (controller.node.content.last as? BrowserWebContent)?.webView return (controller.node.content.last as? BrowserWebContent)?.webView
} else { } else {
self.pushContent(subject) self.pushContent(subject, nil)
} }
return nil return nil
} }

View File

@ -26,6 +26,7 @@ swift_library(
"//submodules/LocationResources:LocationResources", "//submodules/LocationResources:LocationResources",
"//submodules/UndoUI:UndoUI", "//submodules/UndoUI:UndoUI",
"//submodules/TranslateUI:TranslateUI", "//submodules/TranslateUI:TranslateUI",
"//submodules/Tuples:Tuples",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -27,7 +27,7 @@ public final class InstantPageAnchorItem: InstantPageItem {
public func drawInTile(context: CGContext) { 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 return nil
} }

View File

@ -36,7 +36,7 @@ public final class InstantPageArticleItem: InstantPageItem {
self.hasRTL = hasRTL 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) 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)
} }

View File

@ -23,7 +23,7 @@ public final class InstantPageAudioItem: InstantPageItem {
self.medias = [media] 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) return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
} }

View File

@ -22,6 +22,7 @@ public final class InstantPageContentNode : ASDisplayNode {
private let openUrl: (InstantPageUrlItem) -> Void private let openUrl: (InstantPageUrlItem) -> Void
private let activatePinchPreview: ((PinchSourceContainerNode) -> Void)? private let activatePinchPreview: ((PinchSourceContainerNode) -> Void)?
private let pinchPreviewFinished: ((InstantPageNode) -> Void)? private let pinchPreviewFinished: ((InstantPageNode) -> Void)?
private let getPreloadedResource: (String) -> Data?
var currentLayoutTiles: [InstantPageTile] = [] var currentLayoutTiles: [InstantPageTile] = []
var currentLayoutItemsWithNodes: [InstantPageItem] = [] var currentLayoutItemsWithNodes: [InstantPageItem] = []
@ -42,7 +43,7 @@ public final class InstantPageContentNode : ASDisplayNode {
private var previousVisibleBounds: CGRect? 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.context = context
self.strings = strings self.strings = strings
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
@ -55,6 +56,7 @@ public final class InstantPageContentNode : ASDisplayNode {
self.pinchPreviewFinished = pinchPreviewFinished self.pinchPreviewFinished = pinchPreviewFinished
self.openPeer = openPeer self.openPeer = openPeer
self.openUrl = openUrl self.openUrl = openUrl
self.getPreloadedResource = getPreloadedResource
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
self.contentSize = contentSize self.contentSize = contentSize
@ -210,7 +212,7 @@ public final class InstantPageContentNode : ASDisplayNode {
}, updateWebEmbedHeight: { _ in }, updateWebEmbedHeight: { _ in
}, updateDetailsExpanded: { [weak self] expanded in }, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded) self?.updateDetailsExpanded(detailsIndex, expanded)
}, currentExpandedDetails: self.currentExpandedDetails) { }, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: self.getPreloadedResource) {
newNode.frame = itemFrame newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition) newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode { if let topNode = topNode {

View File

@ -649,7 +649,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
self?.updateWebEmbedHeight(embedIndex, height) self?.updateWebEmbedHeight(embedIndex, height)
}, updateDetailsExpanded: { [weak self] expanded in }, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded) self?.updateDetailsExpanded(detailsIndex, expanded)
}, currentExpandedDetails: self.currentExpandedDetails) { }, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: { _ in return nil }) {
newNode.frame = itemFrame newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition) newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode { if let topNode = topNode {

View File

@ -39,12 +39,12 @@ public final class InstantPageDetailsItem: InstantPageItem {
self.index = index 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? var expanded: Bool?
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] { if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
expanded = currentlyExpanded 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 { public func matchesAnchor(_ anchor: String) -> Bool {

View File

@ -35,7 +35,7 @@ public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
public var requestLayoutUpdate: ((Bool) -> Void)? 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.context = context
self.strings = strings self.strings = strings
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
@ -65,7 +65,7 @@ public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded) self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded)
self.separatorNode = ASDisplayNode() 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() super.init()

View File

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

View File

@ -20,7 +20,7 @@ public final class InstantPageFeedbackItem: InstantPageItem {
self.webPage = webPage 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) return InstantPageFeedbackNode(context: context, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
} }

View File

@ -44,8 +44,8 @@ public final class InstantPageImageItem: InstantPageItem {
self.fit = fit 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? { 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) 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 { public func matchesAnchor(_ anchor: String) -> Bool {

View File

@ -14,6 +14,7 @@ import LiveLocationPositionNode
import AppBundle import AppBundle
import TelegramUIPreferences import TelegramUIPreferences
import ContextUI import ContextUI
import Tuples
private struct FetchControls { private struct FetchControls {
let fetch: (Bool) -> Void let fetch: (Bool) -> Void
@ -48,7 +49,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
private var themeUpdated: Bool = false 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.context = context
self.theme = theme self.theme = theme
self.webPage = webPage self.webPage = webPage
@ -72,51 +73,103 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.addSubnode(self.pinchContainerNode) self.addSubnode(self.pinchContainerNode)
if case let .image(image) = media.media, let largest = largestImageRepresentation(image.representations) { if case let .image(image) = media.media, let largest = largestImageRepresentation(image.representations) {
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) if let externalResource = largest.resource as? InstantPageExternalMediaResource {
self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) var url = externalResource.url
if !url.hasPrefix("http") && !url.hasPrefix("https") {
if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: image) { url = "https:\(url)"
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())
} }
}, cancel: { let photoData: Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError>
chatMessagePhotoCancelInteractiveFetch(account: context.account, photoReference: imageReference) if let preloadedData = getPreloadedResource(externalResource.url) {
}) photoData = .single(Tuple4(nil, preloadedData, .full, true))
} else {
if interactive { photoData = context.engine.resources.httpData(url: url, preserveExactUrl: true)
self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in |> map(Optional.init)
displayLinkDispatcher.dispatch { |> `catch` { _ -> Signal<Data?, NoError> in
if let strongSelf = self { return .single(nil)
strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) }
strongSelf.updateFetchStatus() |> map { data in
if let data {
return Tuple4(nil, data, .full, true)
} else {
return Tuple4(nil, nil, .full, false)
} }
} }
}))
if media.url != nil {
self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink")
self.pinchContainerNode.contentNode.addSubnode(self.linkIconNode)
} }
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: photoData)
self.pinchContainerNode.contentNode.addSubnode(self.statusNode) |> 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 !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())
}
}, 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 { } else if case let .file(file) = media.media {
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) if let externalResource = file.resource as? InstantPageExternalMediaResource {
if file.mimeType.hasPrefix("image/") { let photoData: Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError>
if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) { if let preloadedData = getPreloadedResource(externalResource.url) {
_ = freeMediaFileInteractiveFetched(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference).start() 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 { } else {
self.imageNode.setSignal(chatMessageVideo(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, videoReference: fileReference)) let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
} if file.mimeType.hasPrefix("image/") {
if file.isVideo { if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) {
self.statusNode.transitionToState(.play(.white), animated: false, completion: {}) _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference).start()
self.pinchContainerNode.contentNode.addSubnode(self.statusNode) }
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 { } else if case let .geo(map) = media.media {
self.addSubnode(self.pinNode) self.addSubnode(self.pinNode)

View File

@ -15,7 +15,7 @@ public protocol InstantPageItem {
func matchesAnchor(_ anchor: String) -> Bool func matchesAnchor(_ anchor: String) -> Bool
func drawInTile(context: CGContext) 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 matchesNode(_ node: InstantPageNode) -> Bool
func linkSelectionRects(at point: CGPoint) -> [CGRect] func linkSelectionRects(at point: CGPoint) -> [CGRect]

View File

@ -884,9 +884,11 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, userLoc
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil) let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
contentSize.height += closingSpacing contentSize.height += closingSpacing
let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0), webPage: webPage) if webPage.webpageId.id != 0 {
contentSize.height += feedbackItem.frame.height let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0), webPage: webPage)
items.append(feedbackItem) contentSize.height += feedbackItem.frame.height
items.append(feedbackItem)
}
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
} }

View File

@ -26,7 +26,7 @@ public final class InstantPagePeerReferenceItem: InstantPageItem {
self.rtl = rtl 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) return InstantPagePeerReferenceNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer)
} }

View File

@ -28,7 +28,7 @@ public final class InstantPagePlayableVideoItem: InstantPageItem {
self.interactive = interactive 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) return InstantPagePlayableVideoNode(context: context, userLocation: sourceLocation.userLocation, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia)
} }

View File

@ -203,7 +203,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
let sideInset: CGFloat = 16.0 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 (_, 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))) 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.contentContainerNode.insertSubnode(contentNode, at: 0)
self.contentNode = contentNode self.contentNode = contentNode

View File

@ -61,7 +61,7 @@ public final class InstantPageShapeItem: InstantPageItem {
return false 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 return nil
} }

View File

@ -20,7 +20,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
self.medias = medias 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) return InstantPageSlideshowNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished)
} }

View File

@ -187,7 +187,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, ASScrollViewDe
let media = self.items[index] let media = self.items[index]
let contentNode: ASDisplayNode let contentNode: ASDisplayNode
if case .image = media.media { 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 { } else if case .file = media.media {
contentNode = ASDisplayNode() contentNode = ASDisplayNode()
} else { } else {

View File

@ -198,7 +198,7 @@ final class InstantPageSubContentNode : ASDisplayNode {
}, updateWebEmbedHeight: { _ in }, updateWebEmbedHeight: { _ in
}, updateDetailsExpanded: { [weak self] expanded in }, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded) self?.updateDetailsExpanded(detailsIndex, expanded)
}, currentExpandedDetails: self.currentExpandedDetails) { }, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: { _ in return nil }) {
newNode.frame = itemFrame newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition) newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode { if let topNode = topNode {

View File

@ -199,12 +199,12 @@ public final class InstantPageTableItem: InstantPageScrollableItem {
return false 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] = [] var additionalNodes: [InstantPageNode] = []
for cell in self.cells { for cell in self.cells {
for item in cell.additionalItems { for item in cell.additionalItems {
if item.wantsNode { 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) node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
additionalNodes.append(node) additionalNodes.append(node)
} }

View File

@ -435,7 +435,7 @@ public final class InstantPageTextItem: InstantPageItem {
return false 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 return nil
} }
@ -484,11 +484,11 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem {
context.restoreGState() 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] = [] var additionalNodes: [InstantPageNode] = []
for item in additionalItems { for item in additionalItems {
if item.wantsNode { 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 node.frame = item.frame
additionalNodes.append(node) additionalNodes.append(node)
} }

View File

@ -100,12 +100,12 @@ final class InstantPageTextStyleStack {
} }
case .subscript: case .subscript:
if baselineOffset == nil { if baselineOffset == nil {
baselineOffset = 0.35 baselineOffset = -0.35
underline = false underline = false
} }
case .superscript: case .superscript:
if baselineOffset == nil { if baselineOffset == nil {
baselineOffset = -0.35 baselineOffset = 0.35
} }
case let .markerColor(color): case let .markerColor(color):
if markerColor == nil { if markerColor == nil {

View File

@ -24,7 +24,7 @@ public final class InstantPageWebEmbedItem: InstantPageItem {
self.enableScrolling = enableScrolling 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) return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
} }

View File

@ -81,11 +81,21 @@ public func presentGiveawayInfoController(
onlyNewSubscribers = true 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 var isGroup = false
if let channel = author as? TelegramChannel, case .group = channel.info { if let channel = author as? TelegramChannel, case .group = channel.info {
isGroup = true isGroup = true
} }
var peerName = ""
if let author {
peerName = EnginePeer(author).compactDisplayTitle
}
var groupsAndChannels = false var groupsAndChannels = false
var channelsCount: Int32 = 1 var channelsCount: Int32 = 1
@ -113,10 +123,7 @@ public func presentGiveawayInfoController(
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var peerName = ""
if let channel = author as? TelegramChannel {
peerName = EnginePeer(channel).compactDisplayTitle
}
let timeZone = TimeZone.current let timeZone = TimeZone.current
let untilDate = stringForDate(timestamp: untilDateValue, timeZone: timeZone, strings: presentationData.strings) let untilDate = stringForDate(timestamp: untilDateValue, timeZone: timeZone, strings: presentationData.strings)

View File

@ -3,8 +3,12 @@ import Postbox
import SwiftSignalKit import SwiftSignalKit
import MtProtoKit import MtProtoKit
public func fetchHttpResource(url: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { public func fetchHttpResource(url: String, preserveExactUrl: Bool = false) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
if let urlString = url.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed), let url = URL(string: urlString) { 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)! let signal = MTHttpRequestOperation.data(forHttpUrl: url)!
return Signal { subscriber in return Signal { subscriber in
subscriber.putNext(.reset) subscriber.putNext(.reset)

View File

@ -341,8 +341,8 @@ public extension TelegramEngine {
} }
} }
public func httpData(url: String) -> Signal<Data, EngineMediaResource.Fetch.Error> { public func httpData(url: String, preserveExactUrl: Bool = false) -> Signal<Data, EngineMediaResource.Fetch.Error> {
return fetchHttpResource(url: url) return fetchHttpResource(url: url, preserveExactUrl: preserveExactUrl)
|> mapError { _ -> EngineMediaResource.Fetch.Error in |> mapError { _ -> EngineMediaResource.Fetch.Error in
return .generic return .generic
} }

View File

@ -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 { if case let .webPage(id, url) = webPage.content {
return network.request(Api.functions.messages.getWebPage(url: url, hash: 0)) return network.request(Api.functions.messages.getWebPage(url: url, hash: 0))
|> map(Optional.init) |> map(Optional.init)
@ -270,14 +270,20 @@ func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: Eng
return .single(nil) return .single(nil)
} }
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in |> 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 { if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage), case .Loaded = updatedWebpage.content {
return postbox.transaction { transaction -> TelegramMediaWebpage? in if updatedWebpage.webpageId.id == id {
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) return postbox.transaction { transaction -> TelegramMediaWebpage? in
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
if transaction.getMedia(updatedWebpage.webpageId) != nil { updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
updateMessageMedia(transaction: transaction, id: updatedWebpage.webpageId, media: updatedWebpage) 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 { } else {
return .single(nil) return .single(nil)

View File

@ -79,6 +79,11 @@ private final class NavigationContainer: UIView, UIGestureRecognizerDelegate {
} }
public final class NavigationStackComponent<ChildEnvironment: Equatable>: Component { public final class NavigationStackComponent<ChildEnvironment: Equatable>: Component {
public enum CurlTransition {
case show
case hide
}
public let items: [AnyComponentWithIdentity<ChildEnvironment>] public let items: [AnyComponentWithIdentity<ChildEnvironment>]
public let requestPop: () -> Void public let requestPop: () -> Void
@ -105,7 +110,7 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
super.init(frame: frame) super.init(frame: frame)
self.dimView.alpha = 0.0 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.dimView.isUserInteractionEnabled = false
self.addSubview(self.dimView) self.addSubview(self.dimView)
} }
@ -166,11 +171,20 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
self.component = component self.component = component
self.state = state 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 let navigationTransitionFraction = self.navigationContainer.transitionFraction
self.navigationContainer.isNavigationEnabled = component.items.count > 1 self.navigationContainer.isNavigationEnabled = component.items.count > 1
var validItemIds: [AnyHashable] = [] var validItemIds: [AnyHashable] = []
var removeImpl: (() -> Void)?
var readyItems: [ReadyItem] = [] var readyItems: [ReadyItem] = []
for i in 0 ..< component.items.count { for i in 0 ..< component.items.count {
let item = component.items[i] let item = component.items[i]
@ -184,6 +198,7 @@ public final class NavigationStackComponent<ChildEnvironment: Equatable>: Compon
} else { } else {
itemTransition = itemTransition.withAnimation(.none) itemTransition = itemTransition.withAnimation(.none)
itemView = ItemView() itemView = ItemView()
itemView.clipsToBounds = true
self.itemViews[itemId] = itemView self.itemViews[itemId] = itemView
itemView.contents.parentState = state 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.setFrame(view: readyItem.itemView.dimView, frame: CGRect(origin: .zero, size: availableSize))
readyItem.itemTransition.setAlpha(view: readyItem.itemView.dimView, alpha: 1.0 - alphaTransitionFraction) 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) 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) removedItemIds.append(id)
} }
} }
for id in removedItemIds {
guard let itemView = self.itemViews[id] else { removeImpl = {
continue for id in removedItemIds {
} guard let itemView = self.itemViews[id] else {
if let itemComponeentView = itemView.contents.view { continue
var position = itemComponeentView.center }
position.x += itemComponeentView.bounds.width if let itemComponentView = itemView.contents.view, curlTransition != .show {
transition.setPosition(view: itemComponeentView, position: position, completion: { _ in 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() itemView.removeFromSuperview()
self.itemViews.removeValue(forKey: id) 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) let contentSize = CGSize(width: availableSize.width, height: contentHeight)
self.navigationContainer.frame = CGRect(origin: .zero, size: contentSize) self.navigationContainer.frame = CGRect(origin: .zero, size: contentSize)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -402,7 +402,7 @@ func makeInstantPageControllerImpl(context: AccountContext, message: Message, so
} }
func makeInstantPageControllerImpl(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController { 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) { func openChatWallpaperImpl(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {