mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
no message
This commit is contained in:
@@ -1,37 +1,55 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SafariServices
|
||||
|
||||
final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let account: Account
|
||||
private var settings: InstantPagePresentationSettings?
|
||||
private var strings: PresentationStrings
|
||||
private var theme: InstantPageTheme?
|
||||
private let present: (ViewController, Any?) -> Void
|
||||
private let openPeer: (PeerId) -> Void
|
||||
|
||||
private var webPage: TelegramMediaWebpage?
|
||||
|
||||
|
||||
private var containerLayout: ContainerViewLayout?
|
||||
private let statusBar: StatusBar
|
||||
private let navigationBar: InstantPageNavigationBar
|
||||
private let scrollNode: ASScrollNode
|
||||
private let scrollNodeHeader: ASDisplayNode
|
||||
private var linkHighlightingNode: LinkHighlightingNode?
|
||||
private var textSelectionNode: LinkHighlightingNode?
|
||||
private var settingsNode: InstantPageSettingsNode?
|
||||
private var settingsDimNode: ASDisplayNode?
|
||||
|
||||
var currentLayout: InstantPageLayout?
|
||||
var currentLayoutTiles: [InstantPageTile] = []
|
||||
var currentLayoutItemsWithViews: [InstantPageItem] = []
|
||||
var currentLayoutItemsWithLinks: [InstantPageItem] = []
|
||||
var distanceThresholdGroupCount: [Int: Int] = [:]
|
||||
|
||||
var visibleTiles: [Int: InstantPageTileNode] = [:]
|
||||
var visibleItemsWithViews: [Int: InstantPageNode] = [:]
|
||||
var visibleLinkSelectionViews: [Int: InstantPageLinkSelectionView] = [:]
|
||||
|
||||
var previousContentOffset: CGPoint?
|
||||
var isDeceleratingBecauseOfDragging = false
|
||||
|
||||
init(account: Account, strings: PresentationStrings, statusBar: StatusBar) {
|
||||
private let hiddenMediaDisposable = MetaDisposable()
|
||||
private let resolveUrlDisposable = MetaDisposable()
|
||||
|
||||
init(account: Account, settings: InstantPagePresentationSettings?, strings: PresentationStrings, statusBar: StatusBar, present: @escaping (ViewController, Any?) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) {
|
||||
self.account = account
|
||||
self.strings = strings
|
||||
self.settings = settings
|
||||
self.theme = settings.flatMap(instantPageThemeForSettings)
|
||||
|
||||
self.statusBar = statusBar
|
||||
self.present = present
|
||||
self.openPeer = openPeer
|
||||
|
||||
self.navigationBar = InstantPageNavigationBar(strings: strings)
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNodeHeader = ASDisplayNode()
|
||||
@@ -43,11 +61,129 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
return UITracingLayerView()
|
||||
})
|
||||
|
||||
self.backgroundColor = .white
|
||||
if let theme = self.theme {
|
||||
self.backgroundColor = theme.pageBackgroundColor
|
||||
}
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.scrollNode.addSubnode(self.scrollNodeHeader)
|
||||
self.addSubnode(self.navigationBar)
|
||||
self.scrollNode.view.delaysContentTouches = false
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
self.navigationBar.back = navigateBack
|
||||
self.navigationBar.share = { [weak self] in
|
||||
if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
||||
var shareImpl: (([PeerId]) -> Void)?
|
||||
let shareController = ShareController(account: account, shareAction: { peerIds in
|
||||
shareImpl?(peerIds)
|
||||
}, defaultAction: nil)
|
||||
strongSelf.present(shareController, nil)
|
||||
shareImpl = { [weak shareController] peerIds in
|
||||
shareController?.dismiss()
|
||||
|
||||
for peerId in peerIds {
|
||||
let _ = enqueueMessages(account: account, peerId: peerId, messages: [.message(text: content.url, attributes: [], media: nil, replyToMessageId: nil)]).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.navigationBar.settings = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentSettings()
|
||||
}
|
||||
}
|
||||
self.navigationBar.scrollToTop = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -strongSelf.scrollNode.view.contentInset.top), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.hiddenMediaDisposable.dispose()
|
||||
self.resolveUrlDisposable.dispose()
|
||||
}
|
||||
|
||||
func update(settings: InstantPagePresentationSettings, strings: PresentationStrings) {
|
||||
if self.settings != settings || self.strings !== strings {
|
||||
let previousSettings = self.settings
|
||||
var updateLayout = previousSettings == nil
|
||||
|
||||
self.settings = settings
|
||||
let theme = instantPageThemeForSettings(settings)
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
var animated = false
|
||||
if let previousSettings = previousSettings {
|
||||
if previousSettings.themeType != settings.themeType {
|
||||
updateLayout = true
|
||||
animated = true
|
||||
}
|
||||
if previousSettings.fontSize != settings.fontSize || previousSettings.forceSerif != settings.forceSerif {
|
||||
animated = false
|
||||
updateLayout = true
|
||||
}
|
||||
}
|
||||
|
||||
self.backgroundColor = theme.pageBackgroundColor
|
||||
|
||||
if updateLayout {
|
||||
if animated {
|
||||
if let snapshotView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
self.view.insertSubview(snapshotView, aboveSubview: self.scrollNode.view)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.updateLayout()
|
||||
|
||||
for (_, itemNode) in self.visibleItemsWithViews {
|
||||
itemNode.update(strings: strings, theme: theme)
|
||||
}
|
||||
|
||||
self.updateVisibleItems()
|
||||
self.updateNavigationBar()
|
||||
|
||||
self.recursivelyEnsureDisplaySynchronously(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
recognizer.delaysTouchesBegan = false
|
||||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
if let currentLayout = strongSelf.currentLayout {
|
||||
for item in currentLayout.items {
|
||||
if item.frame.contains(point) {
|
||||
if item is InstantPagePeerReferenceItem {
|
||||
return .fail
|
||||
} else if item is InstantPageAudioItem {
|
||||
return .fail
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .waitForSingleTap
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateTouchesAtPoint(point)
|
||||
}
|
||||
}
|
||||
self.scrollNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
func updateWebPage(_ webPage: TelegramMediaWebpage?) {
|
||||
@@ -67,18 +203,30 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = layout
|
||||
|
||||
if let settingsDimNode = self.settingsDimNode {
|
||||
transition.updateFrame(node: settingsDimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
}
|
||||
|
||||
if let settingsNode = self.settingsNode {
|
||||
settingsNode.updateLayout(layout: layout, transition: transition)
|
||||
transition.updateFrame(node: settingsNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
}
|
||||
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
let scrollInsetTop = 44.0 + statusBarHeight
|
||||
|
||||
let resetOffset = self.scrollNode.bounds.size.width.isZero
|
||||
let widthUpdated = !self.scrollNode.bounds.size.width.isEqual(to: layout.size.width)
|
||||
|
||||
if self.scrollNode.bounds.size != layout.size || !self.scrollNode.view.contentInset.top.isEqual(to: scrollInsetTop) {
|
||||
if !self.scrollNode.bounds.size.width.isEqual(to: layout.size.width) {
|
||||
self.updateLayout()
|
||||
}
|
||||
self.scrollNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||
self.scrollNodeHeader.frame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0), size: CGSize(width: layout.size.width, height: 2000.0))
|
||||
self.scrollNode.view.contentInset = UIEdgeInsetsMake(scrollInsetTop, 0.0, 0.0, 0.0)
|
||||
if self.visibleItemsWithViews.isEmpty && self.visibleTiles.isEmpty {
|
||||
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
if resetOffset {
|
||||
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top)
|
||||
}
|
||||
if widthUpdated {
|
||||
self.updateLayout()
|
||||
}
|
||||
self.updateVisibleItems()
|
||||
self.updateNavigationBar()
|
||||
@@ -86,26 +234,20 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
private func updateLayout() {
|
||||
guard let containerLayout = self.containerLayout, let webPage = self.webPage else {
|
||||
guard let containerLayout = self.containerLayout, let webPage = self.webPage, let theme = self.theme else {
|
||||
return
|
||||
}
|
||||
|
||||
let currentLayout = instantPageLayoutForWebPage(webPage, boundingWidth: containerLayout.size.width)
|
||||
let currentLayout = instantPageLayoutForWebPage(webPage, boundingWidth: containerLayout.size.width, strings: self.strings, theme: theme)
|
||||
|
||||
for (_, tileNode) in self.visibleTiles {
|
||||
tileNode.removeFromSupernode()
|
||||
}
|
||||
self.visibleTiles.removeAll()
|
||||
|
||||
for (_, linkView) in self.visibleLinkSelectionViews {
|
||||
linkView.removeFromSuperview()
|
||||
}
|
||||
self.visibleLinkSelectionViews.removeAll()
|
||||
|
||||
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width)
|
||||
|
||||
var currentLayoutItemsWithViews: [InstantPageItem] = []
|
||||
var currentLayoutItemsWithLinks: [InstantPageItem] = []
|
||||
var distanceThresholdGroupCount: [Int: Int] = [:]
|
||||
|
||||
for item in currentLayout.items {
|
||||
@@ -121,26 +263,25 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
distanceThresholdGroupCount[Int(group)] = count + 1
|
||||
}
|
||||
}
|
||||
if item.hasLinks {
|
||||
currentLayoutItemsWithLinks.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
self.currentLayout = currentLayout
|
||||
self.currentLayoutTiles = currentLayoutTiles
|
||||
self.currentLayoutItemsWithViews = currentLayoutItemsWithViews
|
||||
self.currentLayoutItemsWithLinks = currentLayoutItemsWithLinks
|
||||
self.distanceThresholdGroupCount = distanceThresholdGroupCount
|
||||
|
||||
self.scrollNode.view.contentSize = currentLayout.contentSize
|
||||
}
|
||||
|
||||
func updateVisibleItems() {
|
||||
guard let theme = self.theme else {
|
||||
return
|
||||
}
|
||||
|
||||
var visibleTileIndices = Set<Int>()
|
||||
var visibleItemIndices = Set<Int>()
|
||||
var visibleItemLinkIndices = Set<Int>()
|
||||
|
||||
var visibleBounds = self.scrollNode.view.bounds
|
||||
let visibleBounds = self.scrollNode.view.bounds
|
||||
|
||||
var topNode: ASDisplayNode?
|
||||
for node in self.scrollNode.subnodes.reversed() {
|
||||
@@ -160,7 +301,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
visibleTileIndices.insert(tileIndex)
|
||||
|
||||
if visibleTiles[tileIndex] == nil {
|
||||
let tileNode = InstantPageTileNode(tile: tile)
|
||||
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
|
||||
tileNode.frame = tile.frame
|
||||
if let topNode = topNode {
|
||||
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
|
||||
@@ -200,7 +341,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if itemNode == nil {
|
||||
if let itemNode = item.node(account: self.account) {
|
||||
if let itemNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
|
||||
self?.openMedia(media)
|
||||
}, openPeer: { [weak self] peerId in
|
||||
self?.openPeer(peerId)
|
||||
}) {
|
||||
(itemNode as! ASDisplayNode).frame = item.frame
|
||||
if let topNode = topNode {
|
||||
self.scrollNode.insertSubnode(itemNode as! ASDisplayNode, aboveSubnode: topNode)
|
||||
@@ -245,37 +390,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
for index in removeItemIndices {
|
||||
self.visibleItemsWithViews.removeValue(forKey: index)
|
||||
}
|
||||
|
||||
/*
|
||||
itemIndex = -1;
|
||||
for (id<TGInstantPageLayoutItem> item in _currentLayoutItemsWithLinks) {
|
||||
itemIndex++;
|
||||
CGRect itemFrame = item.frame;
|
||||
if (CGRectIntersectsRect(itemFrame, visibleBounds)) {
|
||||
[visibleItemLinkIndices addObject:@(itemIndex)];
|
||||
|
||||
if (_visibleLinkSelectionViews[@(itemIndex)] == nil) {
|
||||
NSArray<TGInstantPageLinkSelectionView *> *linkViews = [item linkSelectionViews];
|
||||
for (TGInstantPageLinkSelectionView *linkView in linkViews) {
|
||||
linkView.itemTapped = _urlItemTapped;
|
||||
|
||||
[_scrollView addSubview:linkView];
|
||||
}
|
||||
_visibleLinkSelectionViews[@(itemIndex)] = linkViews;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableArray *removeItemLinkIndices = [[NSMutableArray alloc] init];
|
||||
[_visibleLinkSelectionViews enumerateKeysAndObjectsUsingBlock:^(NSNumber *nIndex, NSArray<TGInstantPageLinkSelectionView *> *linkViews, __unused BOOL *stop) {
|
||||
if (![visibleItemLinkIndices containsObject:nIndex]) {
|
||||
for (UIView *linkView in linkViews) {
|
||||
[linkView removeFromSuperview];
|
||||
}
|
||||
[removeItemLinkIndices addObject:nIndex];
|
||||
}
|
||||
}];
|
||||
[_visibleLinkSelectionViews removeObjectsForKeys:removeItemLinkIndices];*/
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
@@ -300,6 +414,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let bounds = self.scrollNode.view.bounds
|
||||
let contentOffset = self.scrollNode.view.contentOffset
|
||||
|
||||
var pageProgress: CGFloat = 0.0
|
||||
if !self.scrollNode.view.contentSize.height.isZero {
|
||||
let value = (contentOffset.y + self.scrollNode.view.contentInset.top) / (self.scrollNode.view.contentSize.height - bounds.size.height + self.scrollNode.view.contentInset.top)
|
||||
pageProgress = max(0.0, min(1.0, value))
|
||||
}
|
||||
|
||||
let delta: CGFloat
|
||||
if let previousContentOffset = self.previousContentOffset {
|
||||
delta = contentOffset.y - previousContentOffset.y
|
||||
@@ -308,23 +428,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
self.previousContentOffset = contentOffset
|
||||
|
||||
/*void (^block)(CGRect) = ^(CGRect navigationBarFrame) {
|
||||
_navigationBar.frame = navigationBarFrame;
|
||||
CGFloat navigationBarHeight = _navigationBar.bounds.size.height;
|
||||
if (navigationBarHeight < FLT_EPSILON)
|
||||
navigationBarHeight = 64.0f;
|
||||
|
||||
CGFloat statusBarOffset = -MAX(0.0f, MIN(_statusBarHeight, _statusBarHeight + 44.0f - navigationBarHeight));
|
||||
if (ABS(_statusBarOffset - statusBarOffset) > FLT_EPSILON) {
|
||||
_statusBarOffset = statusBarOffset;
|
||||
if (_statusBarOffsetUpdated) {
|
||||
_statusBarOffsetUpdated(statusBarOffset);
|
||||
}
|
||||
|
||||
_scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(_navigationBar.bounds.size.height, 0.0f, 0.0f, 0.0f);
|
||||
};
|
||||
};*/
|
||||
|
||||
var transition: ContainedViewLayoutTransition = .immediate
|
||||
var navigationBarFrame = self.navigationBar.frame
|
||||
navigationBarFrame.size.width = bounds.size.width
|
||||
@@ -352,21 +455,330 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
assert(true)
|
||||
}
|
||||
|
||||
let statusBarAlpha = min(1.0, max(0.0, (navigationBarFrame.size.height - 20.0) / 44.0))
|
||||
var statusBarAlpha = min(1.0, max(0.0, (navigationBarFrame.size.height - 20.0) / 44.0))
|
||||
transition.updateAlpha(node: self.statusBar, alpha: statusBarAlpha * statusBarAlpha)
|
||||
self.statusBar.verticalOffset = navigationBarFrame.size.height - 64.0
|
||||
|
||||
transition.updateFrame(node: self.navigationBar, frame: navigationBarFrame)
|
||||
self.navigationBar.updateLayout(size: navigationBarFrame.size, transition: transition)
|
||||
self.navigationBar.updateLayout(size: navigationBarFrame.size, pageProgress: pageProgress, transition: transition)
|
||||
|
||||
transition.animateView {
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: navigationBarFrame.size.height, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
}
|
||||
|
||||
/*CGFloat progress = 0.0f;
|
||||
if (_scrollView.contentSize.height > FLT_EPSILON) {
|
||||
progress = MAX(0.0f, MIN(1.0f, (_scrollView.contentOffset.y + _scrollView.contentInset.top) / (_scrollView.contentSize.height - _scrollView.frame.size.height + _scrollView.contentInset.top)));
|
||||
}
|
||||
|
||||
private func updateTouchesAtPoint(_ location: CGPoint?) {
|
||||
var rects: [CGRect]?
|
||||
if let location = location, let currentLayout = self.currentLayout {
|
||||
for item in currentLayout.items {
|
||||
if item.frame.contains(location) {
|
||||
let textNodeFrame = item.frame
|
||||
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY))
|
||||
for i in 0 ..< itemRects.count {
|
||||
itemRects[i] = itemRects[i].offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0)
|
||||
}
|
||||
if !itemRects.isEmpty {
|
||||
rects = itemRects
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let rects = rects {
|
||||
let linkHighlightingNode: LinkHighlightingNode
|
||||
if let current = self.linkHighlightingNode {
|
||||
linkHighlightingNode = current
|
||||
} else {
|
||||
linkHighlightingNode = LinkHighlightingNode(color: UIColor(rgb: 0x007BE8).withAlphaComponent(0.4))
|
||||
linkHighlightingNode.isUserInteractionEnabled = false
|
||||
self.linkHighlightingNode = linkHighlightingNode
|
||||
self.scrollNode.addSubnode(linkHighlightingNode)
|
||||
}
|
||||
linkHighlightingNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)
|
||||
linkHighlightingNode.updateRects(rects)
|
||||
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
||||
self.linkHighlightingNode = nil
|
||||
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||
linkHighlightingNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func textItemAtLocation(_ location: CGPoint) -> InstantPageTextItem? {
|
||||
if let currentLayout = self.currentLayout {
|
||||
for item in currentLayout.items {
|
||||
if let item = item as? InstantPageTextItem, item.frame.contains(location) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func urlForTapLocation(_ location: CGPoint) -> InstantPageUrlItem? {
|
||||
if let item = self.textItemAtLocation(location) {
|
||||
return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
if let url = self.urlForTapLocation(location) {
|
||||
self.openUrl(url)
|
||||
}
|
||||
case .longTap:
|
||||
if let url = self.urlForTapLocation(location) {
|
||||
let actionSheet = ActionSheetController()
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: url.url),
|
||||
ActionSheetButtonItem(title: self.self.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.openUrl(url)
|
||||
}
|
||||
}),
|
||||
ActionSheetButtonItem(title: self.strings.Web_CopyLink, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
UIPasteboard.general.string = url.url
|
||||
}),
|
||||
ActionSheetButtonItem(title: self.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let link = URL(string: url.url) {
|
||||
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
|
||||
}
|
||||
})
|
||||
]), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
self.present(actionSheet, nil)
|
||||
} else if let item = self.textItemAtLocation(location) {
|
||||
let textNodeFrame = item.frame
|
||||
var itemRects = item.lineRects()
|
||||
for i in 0 ..< itemRects.count {
|
||||
itemRects[i] = itemRects[i].offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0)
|
||||
}
|
||||
self.updateTextSelectionRects(itemRects, text: item.plainText())
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTextSelectionRects(_ rects: [CGRect], text: String?) {
|
||||
if let text = text, !rects.isEmpty {
|
||||
let textSelectionNode: LinkHighlightingNode
|
||||
if let current = self.textSelectionNode {
|
||||
textSelectionNode = current
|
||||
} else {
|
||||
textSelectionNode = LinkHighlightingNode(color: UIColor.lightGray.withAlphaComponent(0.4))
|
||||
textSelectionNode.isUserInteractionEnabled = false
|
||||
self.textSelectionNode = textSelectionNode
|
||||
self.scrollNode.addSubnode(textSelectionNode)
|
||||
}
|
||||
textSelectionNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)
|
||||
textSelectionNode.updateRects(rects)
|
||||
|
||||
var coveringRect = rects[0]
|
||||
for i in 1 ..< rects.count {
|
||||
coveringRect = coveringRect.union(rects[i])
|
||||
}
|
||||
|
||||
let controller = ContextMenuController(actions: [ContextMenuAction(content: .text(self.strings.Conversation_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = text
|
||||
})])
|
||||
controller.dismissed = { [weak self] in
|
||||
self?.updateTextSelectionRects([], text: nil)
|
||||
}
|
||||
self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
return (strongSelf.scrollNode, coveringRect.insetBy(dx: -3.0, dy: -3.0))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
textSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
} else if let textSelectionNode = self.textSelectionNode {
|
||||
self.textSelectionNode = nil
|
||||
textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in
|
||||
textSelectionNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func openUrl(_ url: InstantPageUrlItem) {
|
||||
guard let items = self.currentLayout?.items else {
|
||||
return
|
||||
}
|
||||
|
||||
if let webPage = self.webPage, url.webpageId == webPage.id, let anchorRange = url.url.range(of: "#") {
|
||||
let anchor = url.url.substring(from: anchorRange.upperBound)
|
||||
if !anchor.isEmpty {
|
||||
for item in items {
|
||||
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: item.frame.origin.y - self.scrollNode.view.contentInset.top), animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.resolveUrlDisposable.set((resolveUrl(account: self.account, url: url.url) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case let .externalUrl(url):
|
||||
if let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext {
|
||||
applicationContext.applicationBindings.openUrl(url)
|
||||
}
|
||||
default:
|
||||
break
|
||||
/*case let .peer(peerId):
|
||||
strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: nil), fromMessageId: nil)
|
||||
case let .botStart(peerId, payload):
|
||||
strongSelf.openPeer(peerId: peerId, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive)), fromMessageId: nil)
|
||||
case let .groupBotStart(peerId, payload):
|
||||
break
|
||||
case let .channelMessage(peerId, messageId):
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peerId, messageId: messageId))*/
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func openMedia(_ media: InstantPageMedia) {
|
||||
guard let items = self.currentLayout?.items, let webPage = self.webPage else {
|
||||
return
|
||||
}
|
||||
|
||||
if let file = media.media as? TelegramMediaFile, (file.isVoice || file.isMusic) {
|
||||
var medias: [InstantPageMedia] = []
|
||||
for item in items {
|
||||
for itemMedia in item.medias {
|
||||
if let itemFile = itemMedia.media as? TelegramMediaFile, (itemFile.isVoice || itemFile.isMusic) {
|
||||
medias.append(itemMedia)
|
||||
}
|
||||
}
|
||||
}
|
||||
let player = ManagedAudioPlaylistPlayer(audioSessionManager: self.account.telegramApplicationContext.mediaManager.audioSession, overlayMediaManager: self.account.telegramApplicationContext.mediaManager.overlayMediaManager, mediaManager: self.account.telegramApplicationContext.mediaManager, account: self.account, postbox: self.account.postbox, playlist: instantPageAudioPlaylist(account: self.account, webpage: webPage, medias: medias, at: media))
|
||||
self.account.telegramApplicationContext.mediaManager.setPlaylistPlayer(player)
|
||||
player.control(.navigation(.next))
|
||||
return
|
||||
}
|
||||
|
||||
var medias: [InstantPageMedia] = []
|
||||
for item in items {
|
||||
medias.append(contentsOf: item.medias)
|
||||
}
|
||||
|
||||
medias = medias.filter {
|
||||
$0.media is TelegramMediaImage
|
||||
}
|
||||
|
||||
var entries: [InstantPageGalleryEntry] = []
|
||||
for media in medias {
|
||||
entries.append(InstantPageGalleryEntry(index: Int32(media.index), media: media, caption: media.caption ?? "", location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count))))
|
||||
}
|
||||
|
||||
var centralIndex: Int?
|
||||
for i in 0 ..< entries.count {
|
||||
if entries[i].media == media {
|
||||
centralIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let centralIndex = centralIndex {
|
||||
let controller = InstantPageGalleryController(account: self.account, entries: entries, centralIndex: centralIndex, replaceRootController: { _, _ in
|
||||
})
|
||||
self.hiddenMediaDisposable.set((controller.hiddenMedia |> deliverOnMainQueue).start(next: { [weak self] entry in
|
||||
if let strongSelf = self {
|
||||
for (_, itemNode) in strongSelf.visibleItemsWithViews {
|
||||
if let itemNode = itemNode as? InstantPageNode {
|
||||
itemNode.updateHiddenMedia(media: entry?.media)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
self.present(controller, InstantPageGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in
|
||||
if let strongSelf = self {
|
||||
for (_, itemNode) in strongSelf.visibleItemsWithViews {
|
||||
if let itemNode = itemNode as? InstantPageNode {
|
||||
if let transitionNode = itemNode.transitionNode(media: entry.media) {
|
||||
return GalleryTransitionArguments(transitionNode: transitionNode, transitionContainerNode: strongSelf, transitionBackgroundNode: strongSelf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private func presentSettings() {
|
||||
guard let settings = self.settings, let containerLayout = self.containerLayout else {
|
||||
return
|
||||
}
|
||||
if self.settingsNode == nil {
|
||||
let settingsNode = InstantPageSettingsNode(strings: self.strings, settings: settings, applySettings: { [weak self] settings in
|
||||
if let strongSelf = self {
|
||||
strongSelf.update(settings: settings, strings: strongSelf.strings)
|
||||
let _ = updateInstantPagePresentationSettingsInteractively(postbox: strongSelf.account.postbox, { _ in
|
||||
return settings
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
self.addSubnode(settingsNode)
|
||||
self.settingsNode = settingsNode
|
||||
|
||||
let settingsDimNode = ASDisplayNode()
|
||||
settingsDimNode.backgroundColor = UIColor(rgb: 0, alpha: 0.1)
|
||||
settingsDimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingsDimTapped(_:))))
|
||||
self.insertSubnode(settingsDimNode, belowSubnode: self.navigationBar)
|
||||
self.settingsDimNode = settingsDimNode
|
||||
|
||||
settingsDimNode.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
|
||||
settingsNode.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
settingsNode.updateLayout(layout: containerLayout, transition: .immediate)
|
||||
settingsNode.animateIn()
|
||||
settingsDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
self.navigationBar.updateDimmed(true, transition: transition)
|
||||
transition.updateAlpha(node: self.statusBar, alpha: 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func settingsDimTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if let settingsNode = self.settingsNode {
|
||||
self.settingsNode = nil
|
||||
settingsNode.animateOut(completion: { [weak settingsNode] in
|
||||
settingsNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
if let settingsDimNode = self.settingsDimNode {
|
||||
self.settingsDimNode = nil
|
||||
settingsDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak settingsDimNode] _ in
|
||||
settingsDimNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
self.navigationBar.updateDimmed(false, transition: transition)
|
||||
transition.updateAlpha(node: self.statusBar, alpha: 1.0)
|
||||
}
|
||||
[_navigationBar setProgress:progress];*/
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user