Initial in-app browser implementation

This commit is contained in:
Ilya Laktyushin 2023-02-18 00:59:18 +04:00
parent fa0130bf8c
commit bc1930be7c
16 changed files with 2903 additions and 1425 deletions

View File

@ -20,6 +20,12 @@ swift_library(
"//submodules/AppBundle:AppBundle",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/ContextUI:ContextUI",
"//submodules/UndoUI:UndoUI",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/Components/BlurredBackgroundComponent:BlurredBackgroundComponent",
],
visibility = [
"//visibility:public",

View File

@ -1,51 +1,86 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
final class BrowserContentState {
final class BrowserContentState: Equatable {
enum ContentType: Equatable {
case webPage
case instantPage
}
let title: String
let url: String
let estimatedProgress: Double
let isInstant: Bool
let contentType: ContentType
var canGoBack: Bool
var canGoForward: Bool
init(title: String, url: String, estimatedProgress: Double, isInstant: Bool, canGoBack: Bool = false, canGoForward: Bool = false) {
init(
title: String,
url: String,
estimatedProgress: Double,
contentType: ContentType,
canGoBack: Bool = false,
canGoForward: Bool = false
) {
self.title = title
self.url = url
self.estimatedProgress = estimatedProgress
self.isInstant = isInstant
self.contentType = contentType
self.canGoBack = canGoBack
self.canGoForward = canGoForward
}
static func == (lhs: BrowserContentState, rhs: BrowserContentState) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.url != rhs.url {
return false
}
if lhs.estimatedProgress != rhs.estimatedProgress {
return false
}
if lhs.contentType != rhs.contentType {
return false
}
if lhs.canGoBack != rhs.canGoBack {
return false
}
if lhs.canGoForward != rhs.canGoForward {
return false
}
return true
}
func withUpdatedTitle(_ title: String) -> BrowserContentState {
return BrowserContentState(title: title, url: self.url, estimatedProgress: self.estimatedProgress, isInstant: self.isInstant, canGoBack: self.canGoBack, canGoForward: self.canGoForward)
return BrowserContentState(title: title, url: self.url, estimatedProgress: self.estimatedProgress, contentType: self.contentType, canGoBack: self.canGoBack, canGoForward: self.canGoForward)
}
func withUpdatedUrl(_ url: String) -> BrowserContentState {
return BrowserContentState(title: self.title, url: url, estimatedProgress: self.estimatedProgress, isInstant: self.isInstant, canGoBack: self.canGoBack, canGoForward: self.canGoForward)
return BrowserContentState(title: self.title, url: url, estimatedProgress: self.estimatedProgress, contentType: self.contentType, canGoBack: self.canGoBack, canGoForward: self.canGoForward)
}
func withUpdatedEstimatedProgress(_ estimatedProgress: Double) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: estimatedProgress, isInstant: self.isInstant, canGoBack: self.canGoBack, canGoForward: self.canGoForward)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: estimatedProgress, contentType: self.contentType, canGoBack: self.canGoBack, canGoForward: self.canGoForward)
}
func withUpdatedCanGoBack(_ canGoBack: Bool) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, isInstant: self.isInstant, canGoBack: canGoBack, canGoForward: self.canGoForward)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, contentType: self.contentType, canGoBack: canGoBack, canGoForward: self.canGoForward)
}
func withUpdatedCanGoForward(_ canGoForward: Bool) -> BrowserContentState {
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, isInstant: self.isInstant, canGoBack: self.canGoBack, canGoForward: canGoForward)
return BrowserContentState(title: self.title, url: self.url, estimatedProgress: self.estimatedProgress, contentType: self.contentType, canGoBack: self.canGoBack, canGoForward: canGoForward)
}
}
protocol BrowserContent: ASDisplayNode {
protocol BrowserContent: UIView {
var state: Signal<BrowserContentState, NoError> { get }
var onScrollingUpdate: (ContentScrollingUpdate) -> Void { get set }
func navigateBack()
func navigateForward()
@ -58,5 +93,30 @@ protocol BrowserContent: ASDisplayNode {
func scrollToTop()
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition)
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: Transition)
}
struct ContentScrollingUpdate {
public var relativeOffset: CGFloat
public var absoluteOffsetToTopEdge: CGFloat?
public var absoluteOffsetToBottomEdge: CGFloat?
public var isReset: Bool
public var isInteracting: Bool
public var transition: Transition
public init(
relativeOffset: CGFloat,
absoluteOffsetToTopEdge: CGFloat?,
absoluteOffsetToBottomEdge: CGFloat?,
isReset: Bool,
isInteracting: Bool,
transition: Transition
) {
self.relativeOffset = relativeOffset
self.absoluteOffsetToTopEdge = absoluteOffsetToTopEdge
self.absoluteOffsetToBottomEdge = absoluteOffsetToBottomEdge
self.isReset = isReset
self.isInteracting = isInteracting
self.transition = transition
}
}

View File

@ -7,12 +7,12 @@ import AppBundle
import ContextUI
final class BrowserFontSizeContextMenuItem: ContextMenuCustomItem {
private let value: CGFloat
private let decrease: () -> CGFloat
private let increase: () -> CGFloat
private let value: Int32
private let decrease: () -> Int32
private let increase: () -> Int32
private let reset: () -> Void
init(value: CGFloat, decrease: @escaping () -> CGFloat, increase: @escaping () -> CGFloat, reset: @escaping () -> Void) {
init(value: Int32, decrease: @escaping () -> Int32, increase: @escaping () -> Int32, reset: @escaping () -> Void) {
self.value = value
self.decrease = decrease
self.increase = increase
@ -46,17 +46,17 @@ private final class BrowserFontSizeContextMenuItemNode: ASDisplayNode, ContextMe
private let leftSeparatorNode: ASDisplayNode
private let rightSeparatorNode: ASDisplayNode
var value: CGFloat = 1.0 {
var value: Int32 = 100 {
didSet {
self.updateValue()
}
}
private let decrease: () -> CGFloat
private let increase: () -> CGFloat
private let decrease: () -> Int32
private let increase: () -> Int32
private let reset: () -> Void
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, value: CGFloat, decrease: @escaping () -> CGFloat, increase: @escaping () -> CGFloat, reset: @escaping () -> Void) {
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, value: Int32, decrease: @escaping () -> Int32, increase: @escaping () -> Int32, reset: @escaping () -> Void) {
self.presentationData = presentationData
self.value = value
self.decrease = decrease
@ -198,14 +198,14 @@ private final class BrowserFontSizeContextMenuItemNode: ASDisplayNode, ContextMe
}
private func updateValue() {
self.centerTextNode.attributedText = NSAttributedString(string: "\(Int(self.value * 100.0))%", font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
self.centerTextNode.attributedText = NSAttributedString(string: "\(self.value)%", font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
let _ = self.centerTextNode.updateLayout(CGSize(width: 70.0, height: .greatestFiniteMagnitude))
self.leftButtonNode.isEnabled = self.value > 0.5
self.leftButtonNode.isEnabled = self.value > 50
self.leftIconNode.alpha = self.leftButtonNode.isEnabled ? 1.0 : 0.3
self.rightButtonNode.isEnabled = self.value < 2.0
self.rightButtonNode.isEnabled = self.value < 150
self.rightIconNode.alpha = self.rightButtonNode.isEnabled ? 1.0 : 0.3
self.centerButtonNode.isEnabled = self.value != 1.0
self.centerButtonNode.isEnabled = self.value != 100
self.centerTextNode.alpha = self.centerButtonNode.isEnabled ? 1.0 : 0.4
}
@ -255,7 +255,7 @@ private final class BrowserFontSizeContextMenuItemNode: ASDisplayNode, ContextMe
@objc private func centerPressed() {
self.reset()
self.value = 1.0
self.value = 100
}
func canBeHighlighted() -> Bool {

View File

@ -5,14 +5,877 @@
//import Postbox
//import SwiftSignalKit
//import Display
//import ComponentFlow
//import TelegramPresentationData
//import TelegramUIPreferences
//import AccountContext
//import AppBundle
//import InstantPageUI
//
//final class BrowserInstantPageContent: ASDisplayNode, BrowserContent {
// private let instantPageNode: InstantPageContentNode
//final class InstantPageView: UIView, UIScrollViewDelegate {
// private let webPage: TelegramMediaWebpage
// private var initialAnchor: String?
// private var pendingAnchor: String?
// private var initialState: InstantPageStoredState?
//
// private let scrollNode: ASScrollNode
// private let scrollNodeHeader: ASDisplayNode
// private let scrollNodeFooter: ASDisplayNode
// private var linkHighlightingNode: LinkHighlightingNode?
// private var textSelectionNode: LinkHighlightingNode?
//
// var currentLayout: InstantPageLayout?
// var currentLayoutTiles: [InstantPageTile] = []
// var currentLayoutItemsWithNodes: [InstantPageItem] = []
// var distanceThresholdGroupCount: [Int: Int] = [:]
//
// var visibleTiles: [Int: InstantPageTileNode] = [:]
// var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
//
// var currentWebEmbedHeights: [Int : CGFloat] = [:]
// var currentExpandedDetails: [Int : Bool]?
// var currentDetailsItems: [InstantPageDetailsItem] = []
//
// var currentAccessibilityAreas: [AccessibilityAreaNode] = []
//
// init(webPage: TelegramMediaWebpage) {
// self.webPage = webPage
//
// self.scrollNode = ASScrollNode()
//
// super.init(frame: frame)
//
// self.addSubview(self.scrollNode.view)
//
// self.scrollNode.view.delaysContentTouches = false
// self.scrollNode.view.delegate = self
//
// if #available(iOSApplicationExtension 11.0, iOS 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 {
// return strongSelf.tapActionAtPoint(point)
// }
// return .waitForSingleTap
// }
// recognizer.highlight = { [weak self] point in
// if let strongSelf = self {
// strongSelf.updateTouchesAtPoint(point)
// }
// }
// self.scrollNode.view.addGestureRecognizer(recognizer)
// }
//
// required init?(coder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
// }
//
// func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
// if let currentLayout = self.currentLayout {
// for item in currentLayout.items {
// let frame = self.effectiveFrameForItem(item)
// if frame.contains(point) {
// if item is InstantPagePeerReferenceItem {
// return .fail
// } else if item is InstantPageAudioItem {
// return .fail
// } else if item is InstantPageArticleItem {
// return .fail
// } else if item is InstantPageFeedbackItem {
// return .fail
// } else if let item = item as? InstantPageDetailsItem {
// for (_, itemNode) in self.visibleItemsWithNodes {
// if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
// return itemNode.tapActionAtPoint(point.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY))
// }
// }
// }
// if !(item is InstantPageImageItem || item is InstantPagePlayableVideoItem) {
// break
// }
// }
// }
// }
// return .waitForSingleTap
// }
//
// private func updateTouchesAtPoint(_ location: CGPoint?) {
// var rects: [CGRect]?
// if let location = location, let currentLayout = self.currentLayout {
// for item in currentLayout.items {
// let itemFrame = self.effectiveFrameForItem(item)
// if itemFrame.contains(location) {
// var contentOffset = CGPoint()
// if let item = item as? InstantPageScrollableItem {
// contentOffset = self.scrollableContentOffset(item: item)
// }
// var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY))
//
// for i in 0 ..< itemRects.count {
// itemRects[i] = itemRects[i].offsetBy(dx: itemFrame.minX - contentOffset.x, dy: itemFrame.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 {
// let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x007aff).withAlphaComponent(0.4)
// linkHighlightingNode = LinkHighlightingNode(color: highlightColor)
// 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 updatePageLayout() {
// guard let containerLayout = self.containerLayout, let webPage = self.webPage, let theme = self.theme else {
// return
// }
//
// let currentLayout = instantPageLayoutForWebPage(webPage, userLocation: self.sourceLocation.userLocation, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights)
//
// for (_, tileNode) in self.visibleTiles {
// tileNode.removeFromSupernode()
// }
// self.visibleTiles.removeAll()
//
// let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width)
//
// var currentDetailsItems: [InstantPageDetailsItem] = []
// var currentLayoutItemsWithNodes: [InstantPageItem] = []
// var distanceThresholdGroupCount: [Int : Int] = [:]
//
// var expandedDetails: [Int : Bool] = [:]
//
// var detailsIndex = -1
// for item in currentLayout.items {
// if item.wantsNode {
// currentLayoutItemsWithNodes.append(item)
// if let group = item.distanceThresholdGroup() {
// let count: Int
// if let currentCount = distanceThresholdGroupCount[Int(group)] {
// count = currentCount
// } else {
// count = 0
// }
// distanceThresholdGroupCount[Int(group)] = count + 1
// }
// if let detailsItem = item as? InstantPageDetailsItem {
// detailsIndex += 1
// expandedDetails[detailsIndex] = detailsItem.initiallyExpanded
// currentDetailsItems.append(detailsItem)
// }
// }
// }
//
// if var currentExpandedDetails = self.currentExpandedDetails {
// for (index, expanded) in expandedDetails {
// if currentExpandedDetails[index] == nil {
// currentExpandedDetails[index] = expanded
// }
// }
// self.currentExpandedDetails = currentExpandedDetails
// } else {
// self.currentExpandedDetails = expandedDetails
// }
//
// let accessibilityAreas = instantPageAccessibilityAreasFromLayout(currentLayout, boundingWidth: containerLayout.size.width)
//
// self.currentLayout = currentLayout
// self.currentLayoutTiles = currentLayoutTiles
// self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes
// self.currentDetailsItems = currentDetailsItems
// self.distanceThresholdGroupCount = distanceThresholdGroupCount
//
// for areaNode in self.currentAccessibilityAreas {
// areaNode.removeFromSupernode()
// }
// for areaNode in accessibilityAreas {
// self.scrollNode.addSubnode(areaNode)
// }
// self.currentAccessibilityAreas = accessibilityAreas
//
// self.scrollNode.view.contentSize = currentLayout.contentSize
// self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0))
// }
//
// func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
// guard let theme = self.theme else {
// return
// }
//
// var visibleTileIndices = Set<Int>()
// var visibleItemIndices = Set<Int>()
//
// var topNode: ASDisplayNode?
// let topTileNode = topNode
// if let scrollSubnodes = self.scrollNode.subnodes {
// for node in scrollSubnodes.reversed() {
// if let node = node as? InstantPageTileNode {
// topNode = node
// break
// }
// }
// }
//
// var collapseOffset: CGFloat = 0.0
// let transition: ContainedViewLayoutTransition
// if animated {
// transition = .animated(duration: 0.3, curve: .spring)
// } else {
// transition = .immediate
// }
//
// var itemIndex = -1
// var embedIndex = -1
// var detailsIndex = -1
//
// var previousDetailsNode: InstantPageDetailsNode?
//
// for item in self.currentLayoutItemsWithNodes {
// itemIndex += 1
// if item is InstantPageWebEmbedItem {
// embedIndex += 1
// }
// if let imageItem = item as? InstantPageImageItem, imageItem.media.media is TelegramMediaWebpage {
// embedIndex += 1
// }
// if item is InstantPageDetailsItem {
// detailsIndex += 1
// }
//
// var itemThreshold: CGFloat = 0.0
// if let group = item.distanceThresholdGroup() {
// var count: Int = 0
// if let currentCount = self.distanceThresholdGroupCount[group] {
// count = currentCount
// }
// itemThreshold = item.distanceThresholdWithGroupCount(count)
// }
//
// var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset)
// var thresholdedItemFrame = itemFrame
// thresholdedItemFrame.origin.y -= itemThreshold
// thresholdedItemFrame.size.height += itemThreshold * 2.0
//
// if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] {
// let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight
// collapseOffset += itemFrame.height - height
// itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height))
// }
//
// if visibleBounds.intersects(thresholdedItemFrame) {
// visibleItemIndices.insert(itemIndex)
//
// var itemNode = self.visibleItemsWithNodes[itemIndex]
// if let currentItemNode = itemNode {
// if !item.matchesNode(currentItemNode) {
// currentItemNode.removeFromSupernode()
// self.visibleItemsWithNodes.removeValue(forKey: itemIndex)
// itemNode = nil
// }
// }
//
// if itemNode == nil {
// let itemIndex = itemIndex
// let embedIndex = embedIndex
// let detailsIndex = detailsIndex
// if let newNode = item.node(context: self.context, strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, theme: theme, sourceLocation: self.sourceLocation, openMedia: { [weak self] media in
// self?.openMedia(media)
// }, longPressMedia: { [weak self] media in
// self?.longPressMedia(media)
// }, activatePinchPreview: { [weak self] sourceNode in
// guard let strongSelf = self, let controller = strongSelf.controller else {
// return
// }
// let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
// guard let strongSelf = self else {
// return CGRect()
// }
//
// let localRect = CGRect(origin: CGPoint(x: 0.0, y: strongSelf.navigationBar.frame.maxY), size: CGSize(width: strongSelf.bounds.width, height: strongSelf.bounds.height - strongSelf.navigationBar.frame.maxY))
// return strongSelf.view.convert(localRect, to: nil)
// })
// controller.window?.presentInGlobalOverlay(pinchController)
// }, pinchPreviewFinished: { [weak self] itemNode in
// guard let strongSelf = self else {
// return
// }
// for (_, listItemNode) in strongSelf.visibleItemsWithNodes {
// if let listItemNode = listItemNode as? InstantPagePeerReferenceNode {
// if listItemNode.frame.intersects(itemNode.frame) && listItemNode.frame.maxY <= itemNode.frame.maxY + 2.0 {
// listItemNode.layer.animateAlpha(from: 0.0, to: listItemNode.alpha, duration: 0.25)
// break
// }
// }
// }
// }, openPeer: { [weak self] peerId in
// self?.openPeer(peerId)
// }, openUrl: { [weak self] url in
// self?.openUrl(url)
// }, updateWebEmbedHeight: { [weak self] height in
// self?.updateWebEmbedHeight(embedIndex, height)
// }, updateDetailsExpanded: { [weak self] expanded in
// self?.updateDetailsExpanded(detailsIndex, expanded)
// }, currentExpandedDetails: self.currentExpandedDetails) {
// newNode.frame = itemFrame
// newNode.updateLayout(size: itemFrame.size, transition: transition)
// if let topNode = topNode {
// self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode)
// } else {
// self.scrollNode.insertSubnode(newNode, at: 0)
// }
// topNode = newNode
// self.visibleItemsWithNodes[itemIndex] = newNode
// itemNode = newNode
//
// if let itemNode = itemNode as? InstantPageDetailsNode {
// itemNode.requestLayoutUpdate = { [weak self] animated in
// if let strongSelf = self {
// strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: animated)
// }
// }
//
// if let previousDetailsNode = previousDetailsNode {
// if itemNode.frame.minY - previousDetailsNode.frame.maxY < 1.0 {
// itemNode.previousNode = previousDetailsNode
// }
// }
// previousDetailsNode = itemNode
// }
// }
// } else {
// if let itemNode = itemNode, itemNode.frame != itemFrame {
// transition.updateFrame(node: itemNode, frame: itemFrame)
// itemNode.updateLayout(size: itemFrame.size, transition: transition)
// }
// }
//
// if let itemNode = itemNode as? InstantPageDetailsNode {
// itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated)
// }
// }
// }
//
// topNode = topTileNode
//
// var tileIndex = -1
// for tile in self.currentLayoutTiles {
// tileIndex += 1
//
// let tileFrame = effectiveFrameForTile(tile)
// var tileVisibleFrame = tileFrame
// tileVisibleFrame.origin.y -= 400.0
// tileVisibleFrame.size.height += 400.0 * 2.0
// if tileVisibleFrame.intersects(visibleBounds) {
// visibleTileIndices.insert(tileIndex)
//
// if self.visibleTiles[tileIndex] == nil {
// let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
// tileNode.frame = tileFrame
// if let topNode = topNode {
// self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
// } else {
// self.scrollNode.insertSubnode(tileNode, at: 0)
// }
// topNode = tileNode
// self.visibleTiles[tileIndex] = tileNode
// } else {
// if visibleTiles[tileIndex]!.frame != tileFrame {
// transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
// }
// }
// }
// }
//
// if let currentLayout = self.currentLayout {
// let effectiveContentHeight = currentLayout.contentSize.height - collapseOffset
// if effectiveContentHeight != self.scrollNode.view.contentSize.height {
// transition.animateView {
// self.scrollNode.view.contentSize = CGSize(width: currentLayout.contentSize.width, height: effectiveContentHeight)
// }
// let previousFrame = self.scrollNodeFooter.frame
// self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: effectiveContentHeight), size: CGSize(width: previousFrame.width, height: 2000.0))
// transition.animateFrame(node: self.scrollNodeFooter, from: previousFrame)
// }
// }
//
// var removeTileIndices: [Int] = []
// for (index, tileNode) in self.visibleTiles {
// if !visibleTileIndices.contains(index) {
// removeTileIndices.append(index)
// tileNode.removeFromSupernode()
// }
// }
// for index in removeTileIndices {
// self.visibleTiles.removeValue(forKey: index)
// }
//
// var removeItemIndices: [Int] = []
// for (index, itemNode) in self.visibleItemsWithNodes {
// if !visibleItemIndices.contains(index) {
// removeItemIndices.append(index)
// itemNode.removeFromSupernode()
// } else {
// var itemFrame = itemNode.frame
// let itemThreshold: CGFloat = 200.0
// itemFrame.origin.y -= itemThreshold
// itemFrame.size.height += itemThreshold * 2.0
// itemNode.updateIsVisible(visibleBounds.intersects(itemFrame))
// }
// }
// for index in removeItemIndices {
// self.visibleItemsWithNodes.removeValue(forKey: index)
// }
// }
//
// func scrollViewDidScroll(_ scrollView: UIScrollView) {
// self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
// self.previousContentOffset = self.scrollNode.view.contentOffset
// }
//
// func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// self.isDeceleratingBecauseOfDragging = decelerate
// if !decelerate {
// self.updateNavigationBar(forceState: true)
// }
// }
//
// func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// self.isDeceleratingBecauseOfDragging = false
// }
//
// private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
// var contentOffset = CGPoint()
// for (_, itemNode) in self.visibleItemsWithNodes {
// if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item {
// contentOffset = itemNode.contentOffset
// break
// }
// }
// return contentOffset
// }
//
// private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
// for (_, itemNode) in self.visibleItemsWithNodes {
// if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
// return detailsNode
// }
// }
// return nil
// }
//
// private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
// if let node = nodeForDetailsItem(item) {
// return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight)
// } else {
// return item.frame.size
// }
// }
//
// private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
// let layoutOrigin = tile.frame.origin
// var origin = layoutOrigin
// for item in self.currentDetailsItems {
// let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
// if layoutOrigin.y >= item.frame.maxY {
// let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
// origin.y += height - item.frame.height
// }
// }
// return CGRect(origin: origin, size: tile.frame.size)
// }
//
// private func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
// let layoutOrigin = item.frame.origin
// var origin = layoutOrigin
//
// for item in self.currentDetailsItems {
// let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
// if layoutOrigin.y >= item.frame.maxY {
// let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
// origin.y += height - item.frame.height
// }
// }
//
// if let item = item as? InstantPageDetailsItem {
// let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
// let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
// return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height))
// } else {
// return CGRect(origin: origin, size: item.frame.size)
// }
// }
//
// private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
// if let currentLayout = self.currentLayout {
// for item in currentLayout.items {
// let itemFrame = self.effectiveFrameForItem(item)
// if itemFrame.contains(location) {
// if let item = item as? InstantPageTextItem, item.selectable {
// return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
// } else if let item = item as? InstantPageScrollableItem {
// let contentOffset = scrollableContentOffset(item: item)
// if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
// return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
// }
// } else if let item = item as? InstantPageDetailsItem {
// for (_, itemNode) in self.visibleItemsWithNodes {
// if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
// if let (textItem, parentOffset) = itemNode.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) {
// return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y))
// }
// }
// }
// }
// }
// }
// }
// return nil
// }
//
// private func urlForTapLocation(_ location: CGPoint) -> InstantPageUrlItem? {
// if let (item, parentOffset) = self.textItemAtLocation(location) {
// return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX - parentOffset.x, dy: -item.frame.minY - parentOffset.y))
// }
// return nil
// }
//
// private func longPressMedia(_ media: InstantPageMedia) {
// let controller = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in
// if let strongSelf = self, let image = media.media as? TelegramMediaImage {
// let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
// let _ = copyToPasteboard(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start()
// }
// }), ContextMenuAction(content: .text(title: self.strings.Conversation_LinkDialogSave, accessibilityLabel: self.strings.Conversation_LinkDialogSave), action: { [weak self] in
// if let strongSelf = self, let image = media.media as? TelegramMediaImage {
// let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
// let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start()
// }
// }), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
// if let strongSelf = self, let webPage = strongSelf.webPage, let image = media.media as? TelegramMediaImage {
// strongSelf.present(ShareController(context: strongSelf.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil)
// }
// })], catchTapsOutside: true)
// self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
// if let strongSelf = self {
// for (_, itemNode) in strongSelf.visibleItemsWithNodes {
// if let (node, _, _) = itemNode.transitionNode(media: media) {
// return (strongSelf.scrollNode, node.convert(node.bounds, to: strongSelf.scrollNode), strongSelf, strongSelf.bounds)
// }
// }
// }
// return nil
// }))
// }
//
// @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
// switch recognizer.state {
// case .ended:
// if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
// switch gesture {
// case .tap:
// break
//// if let url = self.urlForTapLocation(location) {
//// self.openUrl(url)
//// }
// case .longTap:
// break
//// if let theme = self.theme, let url = self.urlForTapLocation(location) {
//// let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url.url)).count > 1
//// let openText = canOpenIn ? self.strings.Conversation_FileOpenIn : self.strings.Conversation_LinkDialogOpen
//// let actionSheet = ActionSheetController(instantPageTheme: theme)
//// actionSheet.setItemGroups([ActionSheetItemGroup(items: [
//// ActionSheetTextItem(title: url.url),
//// ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
//// actionSheet?.dismissAnimated()
//// if let strongSelf = self {
//// if canOpenIn {
//// strongSelf.openUrlIn(url)
//// } else {
//// strongSelf.openUrl(url)
//// }
//// }
//// }),
//// ActionSheetButtonItem(title: self.strings.ShareMenu_CopyShareLink, 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, font: .bold, action: { [weak actionSheet] in
//// actionSheet?.dismissAnimated()
//// })
//// ])])
//// self.present(actionSheet, nil)
//// } else if let (item, parentOffset) = self.textItemAtLocation(location) {
//// let textFrame = item.frame
//// var itemRects = item.lineRects()
//// for i in 0 ..< itemRects.count {
//// itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textFrame.minX, dy: parentOffset.y + textFrame.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 context = self.context
// let strings = self.strings
// let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
// |> take(1)
// |> deliverOnMainQueue).start(next: { [weak self] sharedData in
// let translationSettings: TranslationSettings
// if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
// translationSettings = current
// } else {
// translationSettings = TranslationSettings.defaultSettings
// }
//
// var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in
// UIPasteboard.general.string = text
//
// if let strongSelf = self {
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
// strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
// }
// }), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
// if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
// strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
// }
// })]
//
// let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
// if canTranslate {
// actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
// let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
// controller.pushController = { [weak self] c in
// (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
// self?.controller?.push(c)
// }
// controller.presentController = { [weak self] c in
// self?.controller?.present(c, in: .window(.root))
// }
// self?.present(controller, nil)
// }))
// }
//
// let controller = ContextMenuController(actions: actions)
// 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), strongSelf, strongSelf.bounds)
// } 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 findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat, Bool, [InstantPageDetailsItem])? {
// for item in items {
// if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
// return (item, -10.0, false, [])
// } else if let item = item as? InstantPageTextItem {
// if let (lineIndex, empty) = item.anchors[anchor] {
// return (item, item.lines[lineIndex].frame.minY - 10.0, !empty, [])
// }
// }
// else if let item = item as? InstantPageTableItem {
// if let (offset, empty) = item.anchors[anchor] {
// return (item, offset - 10.0, !empty, [])
// }
// }
// else if let item = item as? InstantPageDetailsItem {
// if let (foundItem, offset, reference, detailsItems) = self.findAnchorItem(anchor, items: item.items) {
// var detailsItems = detailsItems
// detailsItems.insert(item, at: 0)
// return (foundItem, offset, reference, detailsItems)
// }
// }
// }
// return nil
// }
//
// private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
//// guard let theme = self.theme, let webPage = self.webPage else {
//// return
//// }
////
//// var targetAnchor: InstantPageTextAnchorItem?
//// for (name, (line, _)) in item.anchors {
//// if name == referenceAnchor {
//// let anchors = item.lines[line].anchorItems
//// for anchor in anchors {
//// if anchor.name == referenceAnchor {
//// targetAnchor = anchor
//// break
//// }
//// }
//// }
//// }
////
//// guard let anchorText = targetAnchor?.anchorText else {
//// return
//// }
////
//// let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in
//// self?.openUrl(url)
//// }, openUrlIn: { [weak self] url in
//// self?.openUrlIn(url)
//// }, present: { [weak self] c, a in
//// self?.present(c, a)
//// })
//// self.present(controller, nil)
// }
//
// private func scrollToAnchor(_ anchor: String) {
// guard let items = self.currentLayout?.items else {
// return
// }
//
// if !anchor.isEmpty {
// if let (item, lineOffset, reference, detailsItems) = findAnchorItem(String(anchor), items: items) {
// if let item = item as? InstantPageTextItem, reference {
// self.presentReferenceView(item: item, referenceAnchor: anchor)
// } else {
// var previousDetailsNode: InstantPageDetailsNode?
// var containerOffset: CGFloat = 0.0
// for detailsItem in detailsItems {
// if let previousNode = previousDetailsNode {
// previousNode.contentNode.updateDetailsExpanded(detailsItem.index, true, animated: false)
// let frame = previousNode.effectiveFrameForItem(detailsItem)
// containerOffset += frame.minY
//
// previousDetailsNode = previousNode.contentNode.nodeForDetailsItem(detailsItem)
// previousDetailsNode?.setExpanded(true, animated: false)
// } else {
// self.updateDetailsExpanded(detailsItem.index, true, animated: false)
// let frame = self.effectiveFrameForItem(detailsItem)
// containerOffset += frame.minY
//
// previousDetailsNode = self.nodeForDetailsItem(detailsItem)
// previousDetailsNode?.setExpanded(true, animated: false)
// }
// }
//
// let frame: CGRect
// if let previousDetailsNode = previousDetailsNode {
// frame = previousDetailsNode.effectiveFrameForItem(item)
// } else {
// frame = self.effectiveFrameForItem(item)
// }
//
// var targetY = min(containerOffset + frame.minY + lineOffset, self.scrollNode.view.contentSize.height - self.scrollNode.frame.height)
// if targetY < self.scrollNode.view.contentOffset.y {
// targetY -= self.scrollNode.view.contentInset.top
// } else {
// targetY -= self.containerLayout?.statusBarHeight ?? 20.0
// }
// self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
// }
// } else if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, !instantPage.isComplete {
// self.loadProgress.set(0.5)
// self.pendingAnchor = anchor
// }
// } else {
// self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top), animated: true)
// }
// }
//
// private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) {
// let currentHeight = self.currentWebEmbedHeights[index]
// if height != currentHeight {
// if let currentHeight = currentHeight, currentHeight > height {
// return
// }
// self.currentWebEmbedHeights[index] = height
//
// let signal: Signal<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue()))
// self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
// if let strongSelf = self {
// strongSelf.updateLayout()
// strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds)
// }
// }))
// }
// }
//
// private func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true) {
// if var currentExpandedDetails = self.currentExpandedDetails {
// currentExpandedDetails[index] = expanded
// self.currentExpandedDetails = currentExpandedDetails
// }
// self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: animated)
// }
//
//}
//
//final class BrowserInstantPageContent: UIView, BrowserContent {
// var onScrollingUpdate: (ContentScrollingUpdate) -> Void
//
// func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ComponentFlow.Transition) {
//
// }
//
// private var _state: BrowserContentState
// private let statePromise: Promise<BrowserContentState>
@ -28,7 +891,6 @@
// self.webPage = webPage
//
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
// self.instantPageNode = InstantPageContentNode(context: context, webPage: webPage, settings: nil, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, sourcePeerType: .contact, getNavigationController: { return nil }, present: { _, _ in }, pushController: { _ in }, openPeer: { _ in }, navigateBack: {})
//
// let title: String
// if case let .Loaded(content) = webPage.content {
@ -37,12 +899,16 @@
// title = ""
// }
//
// self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, isInstant: false)
// self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, contentType: .instantPage)
// self.statePromise = Promise<BrowserContentState>(self._state)
//
// super.init()
//
// self.addSubnode(self.instantPageNode)
//
// }
//
// required init?(coder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
// }
//
// func navigateBack() {
@ -78,14 +944,14 @@
// }
//
// func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
// let layout = ContainerViewLayout(size: size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: insets.bottom, right: 0.0), safeInsets: UIEdgeInsets(top: 0.0, left: insets.left, bottom: 0.0, right: insets.right), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)
// self.instantPageNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition)
// self.instantPageNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
// //transition.updateFrame(view: self.webView, frame: CGRect(origin: CGPoint(x: 0.0, y: 56.0), size: CGSize(width: size.width, height: size.height - 56.0)))
//
// if !self.initialized {
// self.initialized = true
// self.instantPageNode.updateWebPage(self.webPage, anchor: nil)
// }
//// let layout = ContainerViewLayout(size: size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: insets.bottom, right: 0.0), safeInsets: UIEdgeInsets(top: 0.0, left: insets.left, bottom: 0.0, right: insets.right), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)
//// self.instantPageNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition)
//// self.instantPageNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
//// //transition.updateFrame(view: self.webView, frame: CGRect(origin: CGPoint(x: 0.0, y: 56.0), size: CGSize(width: size.width, height: size.height - 56.0)))
////
//// if !self.initialized {
//// self.initialized = true
//// self.instantPageNode.updateWebPage(self.webPage, anchor: nil)
//// }
// }
//}

View File

@ -1,35 +0,0 @@
import Foundation
final class BrowserInteraction {
let navigateBack: () -> Void
let navigateForward: () -> Void
let share: () -> Void
let minimize: () -> Void
let openSearch: () -> Void
let updateSearchQuery: (String) -> Void
let dismissSearch: () -> Void
let scrollToPreviousSearchResult: () -> Void
let scrollToNextSearchResult: () -> Void
let decreaseFontSize: () -> Void
let increaseFontSize: () -> Void
let resetFontSize: () -> Void
let updateForceSerif: (Bool) -> Void
init(navigateBack: @escaping () -> Void, navigateForward: @escaping () -> Void, share: @escaping () -> Void, minimize: @escaping () -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, scrollToPreviousSearchResult: @escaping () -> Void, scrollToNextSearchResult: @escaping () -> Void, decreaseFontSize: @escaping () -> Void, increaseFontSize: @escaping () -> Void, resetFontSize: @escaping () -> Void, updateForceSerif: @escaping (Bool) -> Void) {
self.navigateBack = navigateBack
self.navigateForward = navigateForward
self.share = share
self.minimize = minimize
self.openSearch = openSearch
self.updateSearchQuery = updateSearchQuery
self.dismissSearch = dismissSearch
self.scrollToPreviousSearchResult = scrollToPreviousSearchResult
self.scrollToNextSearchResult = scrollToNextSearchResult
self.decreaseFontSize = decreaseFontSize
self.increaseFontSize = increaseFontSize
self.resetFontSize = resetFontSize
self.updateForceSerif = updateForceSerif
}
}

View File

@ -1,416 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import AppBundle
import ContextUI
private let closeImage = generateTintedImage(image: UIImage(bundleImageName: "Instant View/Close"), color: .black)
private let settingsImage = generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings"), color: .black)
private func navigationBarContentNode(for state: BrowserState, currentContentNode: BrowserNavigationBarContentNode?, layoutMetrics: LayoutMetrics, theme: BrowserNavigationBarTheme, strings: PresentationStrings, interaction: BrowserInteraction?) -> BrowserNavigationBarContentNode? {
if let _ = state.search {
if let currentContentNode = currentContentNode as? BrowserNavigationBarSearchContentNode {
currentContentNode.updateState(state)
return currentContentNode
} else {
return BrowserNavigationBarSearchContentNode(theme: theme, strings: strings, state: state, interaction: interaction)
}
}
return nil
}
final class BrowserNavigationBarTheme {
let backgroundColor: UIColor
let separatorColor: UIColor
let primaryTextColor: UIColor
let loadingProgressColor: UIColor
let readingProgressColor: UIColor
let buttonColor: UIColor
let disabledButtonColor: UIColor
let searchBarFieldColor: UIColor
let searchBarTextColor: UIColor
let searchBarPlaceholderColor: UIColor
let searchBarIconColor: UIColor
let searchBarClearColor: UIColor
let searchBarKeyboardColor: PresentationThemeKeyboardColor
init(backgroundColor: UIColor, separatorColor: UIColor, primaryTextColor: UIColor, loadingProgressColor: UIColor, readingProgressColor: UIColor, buttonColor: UIColor, disabledButtonColor: UIColor, searchBarFieldColor: UIColor, searchBarTextColor: UIColor, searchBarPlaceholderColor: UIColor, searchBarIconColor: UIColor, searchBarClearColor: UIColor, searchBarKeyboardColor: PresentationThemeKeyboardColor) {
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
self.primaryTextColor = primaryTextColor
self.loadingProgressColor = loadingProgressColor
self.readingProgressColor = readingProgressColor
self.buttonColor = buttonColor
self.disabledButtonColor = disabledButtonColor
self.searchBarFieldColor = searchBarFieldColor
self.searchBarTextColor = searchBarTextColor
self.searchBarPlaceholderColor = searchBarPlaceholderColor
self.searchBarIconColor = searchBarIconColor
self.searchBarClearColor = searchBarClearColor
self.searchBarKeyboardColor = searchBarKeyboardColor
}
}
protocol BrowserNavigationBarContentNode: ASDisplayNode {
init(theme: BrowserNavigationBarTheme, strings: PresentationStrings, state: BrowserState, interaction: BrowserInteraction?)
func updateState(_ state: BrowserState)
func updateTheme(_ theme: BrowserNavigationBarTheme)
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
}
private final class BrowserLoadingProgressNode: ASDisplayNode {
private var theme: BrowserNavigationBarTheme
private let foregroundNode: ASDisplayNode
init(theme: BrowserNavigationBarTheme) {
self.theme = theme
self.foregroundNode = ASDisplayNode()
self.foregroundNode.backgroundColor = theme.loadingProgressColor
super.init()
self.addSubnode(self.foregroundNode)
}
func updateTheme(_ theme: BrowserNavigationBarTheme) {
self.theme = theme
self.foregroundNode.backgroundColor = theme.loadingProgressColor
}
private var _progress: CGFloat = 0.0
func updateProgress(_ progress: CGFloat, animated: Bool = false) {
if self._progress == progress && animated {
return
}
var animated = animated
if (progress < self._progress && animated) {
animated = false
}
let size = self.bounds.size
self._progress = progress
let transition: ContainedViewLayoutTransition
if animated && progress > 0.0 {
transition = .animated(duration: 0.7, curve: .spring)
} else {
transition = .immediate
}
let alpaTransition: ContainedViewLayoutTransition
if animated {
alpaTransition = .animated(duration: 0.3, curve: .easeInOut)
} else {
alpaTransition = .immediate
}
transition.updateFrame(node: self.foregroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width * progress, height: size.height))
let alpha: CGFloat = progress < 0.001 || progress > 0.999 ? 0.0 : 1.0
alpaTransition.updateAlpha(node: self.foregroundNode, alpha: alpha)
}
}
var browserNavigationBarHeight: CGFloat = 56.0
var browserNavigationBarCollapsedHeight: CGFloat = 24.0
final class BrowserNavigationBar: ASDisplayNode {
private var theme: BrowserNavigationBarTheme
private var strings: PresentationStrings
private var state: BrowserState
var interaction: BrowserInteraction?
private let containerNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let readingProgressNode: ASDisplayNode
private let loadingProgressNode: BrowserLoadingProgressNode
private let closeButton: HighlightableButtonNode
private let closeIconNode: ASImageNode
private let closeIconSmallNode: ASImageNode
let contextSourceNode: ContextExtractedContentContainingNode
private let backButton: HighlightableButtonNode
private let forwardButton: HighlightableButtonNode
private let shareButton: HighlightableButtonNode
private let minimizeButton: HighlightableButtonNode
private let settingsButton: HighlightableButtonNode
private let titleNode: ImmediateTextNode
private let scrollToTopButton: HighlightableButtonNode
private var contentNode: BrowserNavigationBarContentNode?
private let intrinsicSettingsSize: CGSize
private let intrinsicSmallSettingsSize: CGSize
private var validLayout: (CGSize, UIEdgeInsets, LayoutMetrics, CGFloat, CGFloat)?
private var title: (String, Bool) = ("", false) {
didSet {
self.updateTitle()
}
}
private func updateTitle() {
if let (size, insets, layoutMetrics, readingProgress, collapseTransition) = self.validLayout {
self.titleNode.attributedText = NSAttributedString(string: self.title.0, font: Font.with(size: 17.0, design: self.title.1 ? .serif : .regular, weight: .bold), textColor: self.theme.primaryTextColor, paragraphAlignment: .center)
let sideInset: CGFloat = 56.0
self.titleNode.transform = CATransform3DIdentity
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right - sideInset * 2.0, height: size.height))
self.titleNode.frame = CGRect(origin: CGPoint(x: (size.width - titleSize.width) / 2.0, y: size.height - 30.0), size: titleSize)
self.updateLayout(size: size, insets: insets, layoutMetrics: layoutMetrics, readingProgress: readingProgress, collapseTransition: collapseTransition, transition: .immediate)
}
}
var close: (() -> Void)?
var openSettings: (() -> Void)?
var scrollToTop: (() -> Void)?
init(theme: BrowserNavigationBarTheme, strings: PresentationStrings, state: BrowserState) {
self.theme = theme
self.strings = strings
self.state = state
self.containerNode = ASDisplayNode()
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = theme.separatorColor
self.readingProgressNode = ASDisplayNode()
self.readingProgressNode.isLayerBacked = true
self.readingProgressNode.backgroundColor = theme.readingProgressColor
self.closeButton = HighlightableButtonNode()
self.closeButton.allowsGroupOpacity = true
self.closeIconNode = ASImageNode()
self.closeIconNode.displaysAsynchronously = false
self.closeIconNode.displayWithoutProcessing = true
self.closeIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/Close"), color: theme.buttonColor)
self.closeIconSmallNode = ASImageNode()
self.closeIconSmallNode.displaysAsynchronously = false
self.closeIconSmallNode.displayWithoutProcessing = true
self.closeIconSmallNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/CloseSmall"), color: theme.buttonColor)
self.closeIconSmallNode.alpha = 0.0
self.contextSourceNode = ContextExtractedContentContainingNode()
self.settingsButton = HighlightableButtonNode()
self.settingsButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings"), color: theme.buttonColor), for: [])
self.intrinsicSettingsSize = CGSize(width: browserNavigationBarHeight, height: browserNavigationBarHeight)
self.intrinsicSmallSettingsSize = CGSize(width: browserNavigationBarCollapsedHeight, height: browserNavigationBarCollapsedHeight)
self.settingsButton.frame = CGRect(origin: CGPoint(), size: self.intrinsicSettingsSize)
self.backButton = HighlightableButtonNode()
self.backButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Back"), color: theme.buttonColor), for: [])
self.backButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Back"), color: theme.disabledButtonColor), for: [.disabled])
self.forwardButton = HighlightableButtonNode()
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Forward"), color: theme.buttonColor), for: [])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Forward"), color: theme.disabledButtonColor), for: [.disabled])
self.shareButton = HighlightableButtonNode()
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationShare"), color: theme.buttonColor), for: [])
self.minimizeButton = HighlightableButtonNode()
self.minimizeButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Minimize"), color: theme.buttonColor), for: [])
self.titleNode = ImmediateTextNode()
self.titleNode.textAlignment = .center
self.titleNode.maximumNumberOfLines = 1
self.scrollToTopButton = HighlightableButtonNode()
self.loadingProgressNode = BrowserLoadingProgressNode(theme: theme)
super.init()
self.clipsToBounds = true
self.containerNode.backgroundColor = theme.backgroundColor
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.readingProgressNode)
self.containerNode.addSubnode(self.closeButton)
self.closeButton.addSubnode(self.closeIconNode)
self.closeButton.addSubnode(self.closeIconSmallNode)
self.containerNode.addSubnode(self.contextSourceNode)
self.contextSourceNode.addSubnode(self.settingsButton)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.scrollToTopButton)
self.containerNode.addSubnode(self.loadingProgressNode)
self.containerNode.addSubnode(self.separatorNode)
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
self.settingsButton.addTarget(self, action: #selector(self.settingsPressed), forControlEvents: .touchUpInside)
self.scrollToTopButton.addTarget(self, action: #selector(self.scrollToTopPressed), forControlEvents: .touchUpInside)
self.title = (state.content?.title ?? "", state.content?.isInstant ?? false)
}
func updateState(_ state: BrowserState) {
self.state = state
if let (size, insets, layoutMetrics, readingProgress, collapseTransition) = self.validLayout {
self.updateLayout(size: size, insets: insets, layoutMetrics: layoutMetrics, readingProgress: readingProgress, collapseTransition: collapseTransition, transition: .animated(duration: 0.2, curve: .easeInOut))
}
self.title = (state.content?.title ?? "", state.content?.isInstant ?? false)
self.loadingProgressNode.updateProgress(CGFloat(state.content?.estimatedProgress ?? 0.0), animated: true)
}
func updateTheme(_ theme: BrowserNavigationBarTheme) {
guard self.theme !== theme else {
return
}
self.theme = theme
self.containerNode.backgroundColor = theme.backgroundColor
self.separatorNode.backgroundColor = theme.separatorColor
self.closeIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/Close"), color: theme.buttonColor)
self.closeIconSmallNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/CloseSmall"), color: theme.buttonColor)
self.settingsButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings"), color: theme.buttonColor), for: [])
self.readingProgressNode.backgroundColor = theme.readingProgressColor
self.loadingProgressNode.updateTheme(theme)
self.updateTitle()
}
@objc private func closePressed() {
self.close?()
}
@objc private func settingsPressed() {
self.openSettings?()
}
@objc private func scrollToTopPressed() {
self.scrollToTop?()
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, layoutMetrics: LayoutMetrics, readingProgress: CGFloat, collapseTransition: CGFloat, transition: ContainedViewLayoutTransition) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, insets, layoutMetrics, readingProgress, collapseTransition)
var dismissedContentNode: ASDisplayNode?
var immediatelyLayoutContentNodeAndAnimateAppearance = false
if let contentNode = navigationBarContentNode(for: self.state, currentContentNode: self.contentNode, layoutMetrics: layoutMetrics, theme: self.theme, strings: self.strings, interaction: self.interaction) {
if contentNode !== self.contentNode {
dismissedContentNode = self.contentNode
immediatelyLayoutContentNodeAndAnimateAppearance = true
self.containerNode.insertSubnode(contentNode, belowSubnode: self.separatorNode)
self.contentNode = contentNode
}
} else {
dismissedContentNode = self.contentNode
self.contentNode = nil
}
let expandTransition = 1.0 - collapseTransition
let maxBarHeight: CGFloat
let minBarHeight: CGFloat
if insets.top.isZero {
maxBarHeight = browserNavigationBarHeight
minBarHeight = browserNavigationBarCollapsedHeight
} else {
maxBarHeight = insets.top + 44.0
minBarHeight = insets.top + browserNavigationBarCollapsedHeight
}
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -(maxBarHeight - minBarHeight) * collapseTransition), size: size)
transition.updateFrame(node: self.containerNode, frame: containerFrame)
transition.updateFrame(node: self.readingProgressNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: floorToScreenPixels(size.width * readingProgress), height: size.height)))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: size.height)))
if let image = self.closeIconNode.image {
let closeIconSize = image.size
let arrowHeight: CGFloat
if expandTransition < 1.0 {
arrowHeight = floor(12.0 * expandTransition + 18.0)
} else {
arrowHeight = 30.0
}
let scaledIconSize = CGSize(width: closeIconSize.width * arrowHeight / closeIconSize.height, height: arrowHeight)
let arrowOffset = floor(9.0 * expandTransition + 3.0)
transition.updateFrame(node: self.closeIconNode, frame: CGRect(origin: CGPoint(x: insets.left + 8.0, y: size.height - arrowHeight - arrowOffset), size: scaledIconSize))
}
let offsetScaleTransition: CGFloat
let buttonScaleTransition: CGFloat
if expandTransition < 1.0 {
offsetScaleTransition = expandTransition
buttonScaleTransition = ((expandTransition * self.intrinsicSettingsSize.height) + ((1.0 - expandTransition) * self.intrinsicSmallSettingsSize.height)) / self.intrinsicSettingsSize.height
} else {
offsetScaleTransition = 1.0
buttonScaleTransition = 1.0
}
let alphaTransition = min(1.0, offsetScaleTransition * offsetScaleTransition)
let maxSettingsOffset = floor(self.intrinsicSettingsSize.height / 2.0)
let minSettingsOffset = floor(self.intrinsicSmallSettingsSize.height / 2.0)
let settingsOffset = expandTransition * maxSettingsOffset + (1.0 - expandTransition) * minSettingsOffset
transition.updateTransformScale(node: self.titleNode, scale: 0.65 + expandTransition * 0.35)
transition.updatePosition(node: self.titleNode, position: CGPoint(x: size.width / 2.0, y: size.height - settingsOffset))
self.contextSourceNode.frame = CGRect(origin: CGPoint(x: size.width - 56.0, y: 0.0), size: CGSize(width: 56.0, height: 56.0))
transition.updateTransformScale(node: self.settingsButton, scale: buttonScaleTransition)
transition.updatePosition(node: self.settingsButton, position: CGPoint(x: 56.0 - insets.right - buttonScaleTransition * self.intrinsicSettingsSize.width / 2.0, y: size.height - settingsOffset))
transition.updateAlpha(node: self.settingsButton, alpha: alphaTransition)
transition.updateFrame(node: self.scrollToTopButton, frame: CGRect(origin: CGPoint(x: insets.left + 64.0, y: 0.0), size: CGSize(width: size.width - insets.left - insets.right - 64.0 * 2.0, height: size.height)))
let loadingProgressHeight: CGFloat = 2.0
transition.updateFrame(node: self.loadingProgressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - loadingProgressHeight - UIScreenPixel), size: CGSize(width: size.width, height: loadingProgressHeight)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
let constrainedSize = CGSize(width: size.width, height: size.height)
if let contentNode = self.contentNode {
let contentNodeFrame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: constrainedSize)
contentNode.updateLayout(size: constrainedSize, transition: transition)
if immediatelyLayoutContentNodeAndAnimateAppearance {
contentNode.alpha = 0.0
}
transition.updateFrame(node: contentNode, frame: contentNodeFrame)
transition.updateAlpha(node: contentNode, alpha: 1.0)
}
if let dismissedContentNode = dismissedContentNode {
var alphaCompleted = false
let frameCompleted = true
let completed = { [weak self, weak dismissedContentNode] in
if let strongSelf = self, let dismissedContentNode = dismissedContentNode, strongSelf.contentNode === dismissedContentNode {
return
}
if frameCompleted && alphaCompleted {
dismissedContentNode?.removeFromSupernode()
}
}
transition.updateAlpha(node: dismissedContentNode, alpha: 0.0, completion: { _ in
alphaCompleted = true
completed()
})
}
if !hadValidLayout {
self.updateTitle()
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let result = result, result.isDescendant(of: self.containerNode.view) || result == self.containerNode.view {
return result
}
return nil
}
}

View File

@ -0,0 +1,443 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import BlurredBackgroundComponent
import ContextUI
final class BrowserNavigationBarComponent: CombinedComponent {
let backgroundColor: UIColor
let separatorColor: UIColor
let textColor: UIColor
let progressColor: UIColor
let accentColor: UIColor
let topInset: CGFloat
let height: CGFloat
let sideInset: CGFloat
let leftItems: [AnyComponentWithIdentity<Empty>]
let rightItems: [AnyComponentWithIdentity<Empty>]
let centerItem: AnyComponentWithIdentity<Empty>?
let readingProgress: CGFloat
let loadingProgress: Double?
let collapseFraction: CGFloat
init(
backgroundColor: UIColor,
separatorColor: UIColor,
textColor: UIColor,
progressColor: UIColor,
accentColor: UIColor,
topInset: CGFloat,
height: CGFloat,
sideInset: CGFloat,
leftItems: [AnyComponentWithIdentity<Empty>],
rightItems: [AnyComponentWithIdentity<Empty>],
centerItem: AnyComponentWithIdentity<Empty>?,
readingProgress: CGFloat,
loadingProgress: Double?,
collapseFraction: CGFloat
) {
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
self.textColor = textColor
self.progressColor = progressColor
self.accentColor = accentColor
self.topInset = topInset
self.height = height
self.sideInset = sideInset
self.leftItems = leftItems
self.rightItems = rightItems
self.centerItem = centerItem
self.readingProgress = readingProgress
self.loadingProgress = loadingProgress
self.collapseFraction = collapseFraction
}
static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool {
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.separatorColor != rhs.separatorColor {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.progressColor != rhs.progressColor {
return false
}
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.topInset != rhs.topInset {
return false
}
if lhs.height != rhs.height {
return false
}
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.leftItems != rhs.leftItems {
return false
}
if lhs.rightItems != rhs.rightItems {
return false
}
if lhs.centerItem != rhs.centerItem {
return false
}
if lhs.readingProgress != rhs.readingProgress {
return false
}
if lhs.loadingProgress != rhs.loadingProgress {
return false
}
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
return true
}
static var body: Body {
let background = Child(BlurredBackgroundComponent.self)
let readingProgress = Child(Rectangle.self)
let separator = Child(Rectangle.self)
let loadingProgress = Child(LoadingProgressComponent.self)
let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let centerItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
return { context in
var availableWidth = context.availableSize.width
let sideInset: CGFloat = 11.0 + context.component.sideInset
let collapsedHeight: CGFloat = 24.0
let expandedHeight = context.component.height
let contentHeight: CGFloat = expandedHeight * (1.0 - context.component.collapseFraction) + collapsedHeight * context.component.collapseFraction
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
let background = background.update(
component: BlurredBackgroundComponent(color: context.component.backgroundColor),
availableSize: CGSize(width: size.width, height: size.height),
transition: context.transition
)
let readingProgress = readingProgress.update(
component: Rectangle(color: context.component.progressColor),
availableSize: CGSize(width: size.width * context.component.readingProgress, height: size.height),
transition: context.transition
)
let separator = separator.update(
component: Rectangle(color: context.component.separatorColor, height: UIScreenPixel),
availableSize: CGSize(width: size.width, height: size.height),
transition: context.transition
)
let loadingProgressHeight: CGFloat = 2.0
let loadingProgress = loadingProgress.update(
component: LoadingProgressComponent(
color: context.component.accentColor,
height: loadingProgressHeight,
value: context.component.loadingProgress ?? 0.0
),
availableSize: CGSize(width: size.width, height: size.height),
transition: context.transition
)
var leftItemList: [_UpdatedChildComponent] = []
for item in context.component.leftItems {
let item = leftItems[item.id].update(
component: item.component,
availableSize: CGSize(width: availableWidth, height: expandedHeight),
transition: context.transition
)
leftItemList.append(item)
availableWidth -= item.size.width
}
var rightItemList: [_UpdatedChildComponent] = []
for item in context.component.rightItems {
let item = rightItems[item.id].update(
component: item.component,
availableSize: CGSize(width: availableWidth, height: expandedHeight),
transition: context.transition
)
rightItemList.append(item)
availableWidth -= item.size.width
}
let centerItem = context.component.centerItem.flatMap { item in
centerItems[item.id].update(
component: item.component,
availableSize: CGSize(width: availableWidth, height: expandedHeight),
transition: context.transition
)
}
if let centerItem = centerItem {
availableWidth -= centerItem.size.width
}
context.add(background
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
)
context.add(readingProgress
.position(CGPoint(x: readingProgress.size.width / 2.0, y: size.height / 2.0))
)
context.add(separator
.position(CGPoint(x: size.width / 2.0, y: size.height))
)
context.add(loadingProgress
.position(CGPoint(x: size.width / 2.0, y: size.height - loadingProgressHeight / 2.0))
)
var centerLeftInset = sideInset
var leftItemX = sideInset
for item in leftItemList {
context.add(item
.position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0))
.scale(1.0 - 0.35 * context.component.collapseFraction)
.appear(.default(scale: false, alpha: true))
.disappear(.default(scale: false, alpha: true))
)
leftItemX -= item.size.width + 8.0
centerLeftInset += item.size.width + 8.0
}
var centerRightInset = sideInset
var rightItemX = context.availableSize.width - sideInset
for item in rightItemList.reversed() {
context.add(item
.position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0))
.scale(1.0 - 0.35 * context.component.collapseFraction)
.opacity(1.0 - context.component.collapseFraction)
.appear(.default(scale: false, alpha: true))
.disappear(.default(scale: false, alpha: true))
)
rightItemX -= item.size.width + 8.0
centerRightInset += item.size.width + 8.0
}
let maxCenterInset = max(centerLeftInset, centerRightInset)
if let centerItem = centerItem {
context.add(centerItem
.position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0, y: context.component.topInset + contentHeight / 2.0))
.scale(1.0 - 0.35 * context.component.collapseFraction)
.appear(.default(scale: false, alpha: true))
.disappear(.default(scale: false, alpha: true))
)
}
return size
}
}
}
private final class LoadingProgressComponent: Component {
let color: UIColor
let height: CGFloat
let value: CGFloat
init(
color: UIColor,
height: CGFloat,
value: CGFloat
) {
self.color = color
self.height = height
self.value = value
}
static func ==(lhs: LoadingProgressComponent, rhs: LoadingProgressComponent) -> Bool {
if lhs.color != rhs.color {
return false
}
if lhs.height != rhs.height {
return false
}
if lhs.value != rhs.value {
return false
}
return true
}
final class View: UIView {
private var lineView: UIView
private var currentValue: Double = 0.0
init() {
self.lineView = UIView()
self.lineView.clipsToBounds = true
self.lineView.layer.cornerRadius = 1.0
self.lineView.alpha = 0.0
super.init(frame: CGRect())
self.addSubview(self.lineView)
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
func update(component: LoadingProgressComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.lineView.backgroundColor = component.color
let value = component.value
let frame = CGRect(origin: .zero, size: CGSize(width: availableSize.width * component.value, height: component.height))
var animated = true
if value < self.currentValue {
if self.currentValue == 1.0 {
self.lineView.frame = CGRect(origin: .zero, size: CGSize(width: 0.0, height: component.height))
} else {
animated = false
}
}
self.currentValue = value
let transition: Transition
if animated && value > 0.0 {
transition = .spring(duration: 0.7)
} else {
transition = .immediate
}
let alphaTransition: Transition
if animated {
alphaTransition = .easeInOut(duration: 0.3)
} else {
alphaTransition = .immediate
}
transition.setFrame(view: self.lineView, frame: frame)
let alpha: CGFloat = value < 0.01 || value > 0.99 ? 0.0 : 1.0
alphaTransition.setAlpha(view: self.lineView, alpha: alpha)
return CGSize(width: availableSize.width, height: component.height)
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
final class ReferenceButtonComponent: Component {
let content: AnyComponent<Empty>
let tag: AnyObject?
let action: () -> Void
init(
content: AnyComponent<Empty>,
tag: AnyObject? = nil,
action: @escaping () -> Void
) {
self.content = content
self.tag = tag
self.action = action
}
static func ==(lhs: ReferenceButtonComponent, rhs: ReferenceButtonComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.tag !== rhs.tag {
return false
}
return true
}
final class View: HighlightTrackingButton, ComponentTaggedView {
private let sourceView: ContextControllerSourceView
let referenceNode: ContextReferenceContentNode
private let componentView: ComponentView<Empty>
private var component: ReferenceButtonComponent?
public func matches(tag: Any) -> Bool {
if let component = self.component, let componentTag = component.tag {
let tag = tag as AnyObject
if componentTag === tag {
return true
}
}
return false
}
init() {
self.componentView = ComponentView()
self.sourceView = ContextControllerSourceView()
self.sourceView.animateScale = false
self.referenceNode = ContextReferenceContentNode()
super.init(frame: CGRect())
self.sourceView.isUserInteractionEnabled = false
self.addSubview(self.sourceView)
self.sourceView.addSubnode(self.referenceNode)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self, let contentView = strongSelf.componentView.view {
if highlighted {
contentView.layer.removeAnimation(forKey: "opacity")
contentView.alpha = 0.4
} else {
contentView.alpha = 1.0
contentView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.component?.action()
}
func update(component: ReferenceButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.component = component
let componentSize = self.componentView.update(
transition: transition,
component: component.content,
environment: {},
containerSize: availableSize
)
if let componentView = self.componentView.view {
if componentView.superview == nil {
self.referenceNode.view.addSubview(componentView)
}
transition.setFrame(view: componentView, frame: CGRect(origin: .zero, size: componentSize))
}
transition.setFrame(view: self.sourceView, frame: CGRect(origin: .zero, size: componentSize))
self.referenceNode.frame = CGRect(origin: .zero, size: componentSize)
return componentSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -1,79 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import SearchBarNode
import AppBundle
private let searchBarFont = Font.regular(17.0)
private extension SearchBarNodeTheme {
convenience init(navigationBarTheme: BrowserNavigationBarTheme) {
self.init(background: navigationBarTheme.backgroundColor, separator: .clear, inputFill: navigationBarTheme.searchBarFieldColor, primaryText: navigationBarTheme.searchBarTextColor, placeholder: navigationBarTheme.searchBarPlaceholderColor, inputIcon: navigationBarTheme.searchBarIconColor, inputClear: navigationBarTheme.searchBarClearColor, accent: navigationBarTheme.buttonColor, keyboard: navigationBarTheme.searchBarKeyboardColor)
}
}
final class BrowserNavigationBarSearchContentNode: ASDisplayNode, BrowserNavigationBarContentNode {
private var theme: BrowserNavigationBarTheme
private let strings: PresentationStrings
private var state: BrowserState
private var interaction: BrowserInteraction?
private let searchBar: SearchBarNode
init(theme: BrowserNavigationBarTheme, strings: PresentationStrings, state: BrowserState, interaction: BrowserInteraction?) {
self.theme = theme
self.strings = strings
self.state = state
self.interaction = interaction
let searchBarTheme = SearchBarNodeTheme(navigationBarTheme: self.theme)
self.searchBar = SearchBarNode(theme: searchBarTheme, strings: strings, fieldStyle: .modern)
self.searchBar.placeholderString = NSAttributedString(string: "Search on this page", font: searchBarFont, textColor: searchBarTheme.placeholder)
super.init()
self.backgroundColor = theme.backgroundColor
self.addSubnode(self.searchBar)
self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false)
self?.interaction?.dismissSearch()
}
self.searchBar.textUpdated = { [weak self] query, _ in
self?.interaction?.updateSearchQuery(query)
}
}
override func didLoad() {
super.didLoad()
self.searchBar.activate()
}
func updateState(_ state: BrowserState) {
guard let searchState = state.search else {
return
}
self.searchBar.text = searchState.query
}
func updateTheme(_ theme: BrowserNavigationBarTheme) {
guard self.theme !== theme else {
return
}
self.theme = theme
self.backgroundColor = theme.backgroundColor
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(navigationBarTheme: self.theme), strings: self.strings)
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.searchBar.updateLayout(boundingSize: size, leftInset: 0.0, rightInset: 0.0, transition: .immediate)
self.searchBar.frame = CGRect(origin: CGPoint(), size: size)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,357 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import TelegramPresentationData
import AccountContext
import BundleIconComponent
final class SearchBarContentComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let performAction: ActionSlot<BrowserScreen.Action>
init(
theme: PresentationTheme,
strings: PresentationStrings,
performAction: ActionSlot<BrowserScreen.Action>
) {
self.theme = theme
self.strings = strings
self.performAction = performAction
}
static func ==(lhs: SearchBarContentComponent, rhs: SearchBarContentComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
return true
}
final class View: UIView, UITextFieldDelegate {
private final class EmojiSearchTextField: UITextField {
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.integral
}
}
private struct Params: Equatable {
var theme: PresentationTheme
var strings: PresentationStrings
var size: CGSize
static func ==(lhs: Params, rhs: Params) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.size != rhs.size {
return false
}
return true
}
}
private let activated: (Bool) -> Void = { _ in }
private let deactivated: (Bool) -> Void = { _ in }
private let updateQuery: (String?) -> Void = { _ in }
private let backgroundLayer: SimpleLayer
private let iconView: UIImageView
private let clearIconView: UIImageView
private let clearIconButton: HighlightTrackingButton
private let cancelButtonTitle: ComponentView<Empty>
private let cancelButton: HighlightTrackingButton
private var placeholderContent = ComponentView<Empty>()
private var textFrame: CGRect?
private var textField: EmojiSearchTextField?
private var tapRecognizer: UITapGestureRecognizer?
private var params: Params?
private var component: SearchBarContentComponent?
public var wantsDisplayBelowKeyboard: Bool {
return self.textField != nil
}
init() {
self.backgroundLayer = SimpleLayer()
self.iconView = UIImageView()
self.clearIconView = UIImageView()
self.clearIconButton = HighlightableButton()
self.clearIconView.isHidden = true
self.clearIconButton.isHidden = true
self.cancelButtonTitle = ComponentView()
self.cancelButton = HighlightTrackingButton()
super.init(frame: CGRect())
self.layer.addSublayer(self.backgroundLayer)
self.addSubview(self.iconView)
self.addSubview(self.clearIconView)
self.addSubview(self.clearIconButton)
self.addSubview(self.cancelButton)
self.clipsToBounds = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.tapRecognizer = tapRecognizer
self.addGestureRecognizer(tapRecognizer)
self.cancelButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view {
cancelButtonTitleView.layer.removeAnimation(forKey: "opacity")
cancelButtonTitleView.alpha = 0.4
}
} else {
if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view {
cancelButtonTitleView.alpha = 1.0
cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside)
self.clearIconButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity")
strongSelf.clearIconView.alpha = 0.4
} else {
strongSelf.clearIconView.alpha = 1.0
strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.activateTextInput()
}
}
private func activateTextInput() {
if self.textField == nil, let textFrame = self.textFrame {
let backgroundFrame = self.backgroundLayer.frame
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
let textField = EmojiSearchTextField(frame: textFieldFrame)
textField.autocorrectionType = .no
textField.returnKeyType = .search
self.textField = textField
self.insertSubview(textField, belowSubview: self.clearIconView)
textField.delegate = self
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
}
guard !(self.textField?.isFirstResponder ?? false) else {
return
}
self.activated(true)
self.textField?.becomeFirstResponder()
}
@objc private func cancelPressed() {
self.updateQuery(nil)
self.clearIconView.isHidden = true
self.clearIconButton.isHidden = true
let textField = self.textField
self.textField = nil
self.deactivated(textField?.isFirstResponder ?? false)
self.component?.performAction.invoke(.updateSearchActive(false))
if let textField {
textField.resignFirstResponder()
textField.removeFromSuperview()
}
}
@objc private func clearPressed() {
self.updateQuery(nil)
self.textField?.text = ""
self.clearIconView.isHidden = true
self.clearIconButton.isHidden = true
}
func deactivate() {
if let text = self.textField?.text, !text.isEmpty {
self.textField?.endEditing(true)
} else {
self.cancelPressed()
}
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
}
public func textFieldDidEndEditing(_ textField: UITextField) {
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return false
}
@objc private func textFieldChanged(_ textField: UITextField) {
let text = textField.text ?? ""
self.clearIconView.isHidden = text.isEmpty
self.clearIconButton.isHidden = text.isEmpty
self.placeholderContent.view?.isHidden = !text.isEmpty
self.updateQuery(text)
self.component?.performAction.invoke(.updateSearchQuery(text))
if let params = self.params {
self.update(theme: params.theme, strings: params.strings, size: params.size, transition: .immediate)
}
}
func update(component: SearchBarContentComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.component = component
self.update(theme: component.theme, strings: component.strings, size: availableSize, transition: transition)
self.activateTextInput()
return availableSize
}
public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, transition: Transition) {
let params = Params(
theme: theme,
strings: strings,
size: size
)
if self.params == params {
return
}
let isActiveWithText = true
if self.params?.theme !== theme {
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor
}
self.params = params
let sideInset: CGFloat = 10.0
let inputHeight: CGFloat = 36.0
let topInset: CGFloat = (size.height - inputHeight) / 2.0
let sideTextInset: CGFloat = sideInset + 4.0 + 17.0
self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor
self.backgroundLayer.cornerRadius = 10.5
let cancelTextSize = self.cancelButtonTitle.update(
transition: .immediate,
component: AnyComponent(Text(
text: strings.Common_Cancel,
font: Font.regular(17.0),
color: theme.rootController.navigationBar.primaryTextColor
)),
environment: {},
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
)
let cancelButtonSpacing: CGFloat = 8.0
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
if isActiveWithText {
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
}
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
let textX: CGFloat = backgroundFrame.minX + sideTextInset
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
self.textFrame = textFrame
if let image = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 5.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
transition.setFrame(view: self.iconView, frame: iconFrame)
}
let placeholderSize = self.placeholderContent.update(
transition: transition,
component: AnyComponent(
Text(text: strings.Common_Search, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
),
environment: {},
containerSize: size
)
if let placeholderContentView = self.placeholderContent.view {
if placeholderContentView.superview == nil {
self.addSubview(placeholderContentView)
}
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize)
transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame)
}
if let image = self.clearIconView.image {
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
transition.setFrame(view: self.clearIconView, frame: iconFrame)
transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0))
}
if let cancelButtonTitleComponentView = self.cancelButtonTitle.view {
if cancelButtonTitleComponentView.superview == nil {
self.addSubview(cancelButtonTitleComponentView)
cancelButtonTitleComponentView.isUserInteractionEnabled = false
}
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
}
if let textField = self.textField {
textField.textColor = theme.rootController.navigationSearchBar.inputTextColor
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height)))
}
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -1,165 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import AppBundle
private func toolbarContentNode(for state: BrowserState, currentContentNode: BrowserToolbarContentNode?, layoutMetrics: LayoutMetrics, theme: BrowserToolbarTheme, strings: PresentationStrings, interaction: BrowserInteraction?) -> BrowserToolbarContentNode? {
guard case .compact = layoutMetrics.widthClass else {
return nil
}
if let _ = state.search {
if let currentContentNode = currentContentNode as? BrowserToolbarSearchContentNode {
currentContentNode.updateState(state)
return currentContentNode
} else {
return BrowserToolbarSearchContentNode(theme: theme, strings: strings, state: state, interaction: interaction)
}
} else {
if let currentContentNode = currentContentNode as? BrowserToolbarNavigationContentNode {
currentContentNode.updateState(state)
return currentContentNode
} else {
return BrowserToolbarNavigationContentNode(theme: theme, strings: strings, state: state, interaction: interaction)
}
}
}
final class BrowserToolbarTheme {
let backgroundColor: UIColor
let separatorColor: UIColor
let buttonColor: UIColor
let disabledButtonColor: UIColor
init(backgroundColor: UIColor, separatorColor: UIColor, buttonColor: UIColor, disabledButtonColor: UIColor) {
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
self.buttonColor = buttonColor
self.disabledButtonColor = disabledButtonColor
}
}
protocol BrowserToolbarContentNode: ASDisplayNode {
init(theme: BrowserToolbarTheme, strings: PresentationStrings, state: BrowserState, interaction: BrowserInteraction?)
func updateState(_ state: BrowserState)
func updateTheme(_ theme: BrowserToolbarTheme)
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
}
private let toolbarHeight: CGFloat = 49.0
final class BrowserToolbar: ASDisplayNode {
private var theme: BrowserToolbarTheme
private let strings: PresentationStrings
private var state: BrowserState
var interaction: BrowserInteraction?
private let containerNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private var contentNode: BrowserToolbarContentNode?
private var validLayout: (CGFloat, UIEdgeInsets, LayoutMetrics, CGFloat)?
init(theme: BrowserToolbarTheme, strings: PresentationStrings, state: BrowserState) {
self.theme = theme
self.strings = strings
self.state = state
self.containerNode = ASDisplayNode()
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = theme.separatorColor
super.init()
self.clipsToBounds = true
self.containerNode.backgroundColor = theme.backgroundColor
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.separatorNode)
}
func updateState(_ state: BrowserState) {
self.state = state
if let (width, insets, layoutMetrics, collapseTransition) = self.validLayout {
let _ = self.updateLayout(width: width, insets: insets, layoutMetrics: layoutMetrics, collapseTransition: collapseTransition, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
func updateTheme(_ theme: BrowserToolbarTheme) {
guard self.theme !== theme else {
return
}
self.theme = theme
self.containerNode.backgroundColor = theme.backgroundColor
self.separatorNode.backgroundColor = theme.separatorColor
self.contentNode?.updateTheme(theme)
}
func updateLayout(width: CGFloat, insets: UIEdgeInsets, layoutMetrics: LayoutMetrics, collapseTransition: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
self.validLayout = (width, insets, layoutMetrics, collapseTransition)
var dismissedContentNode: ASDisplayNode?
var immediatelyLayoutContentNodeAndAnimateAppearance = false
if let contentNode = toolbarContentNode(for: self.state, currentContentNode: self.contentNode, layoutMetrics: layoutMetrics, theme: self.theme, strings: self.strings, interaction: self.interaction) {
if contentNode !== self.contentNode {
dismissedContentNode = self.contentNode
immediatelyLayoutContentNodeAndAnimateAppearance = true
self.containerNode.insertSubnode(contentNode, belowSubnode: self.separatorNode)
self.contentNode = contentNode
}
} else {
dismissedContentNode = self.contentNode
self.contentNode = nil
}
let effectiveCollapseTransition = self.contentNode == nil ? 1.0 : collapseTransition
let height = toolbarHeight + insets.bottom
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: height * effectiveCollapseTransition), size: CGSize(width: width, height: height))
transition.updateFrame(node: self.containerNode, frame: containerFrame)
transition.updateFrame(node: self.separatorNode, frame: CGRect(x: 0.0, y: 0.0, width: width, height: UIScreenPixel))
let constrainedSize = CGSize(width: width - insets.left - insets.right, height: toolbarHeight)
if let contentNode = self.contentNode {
let contentNodeFrame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: constrainedSize)
contentNode.updateLayout(size: constrainedSize, transition: transition)
if immediatelyLayoutContentNodeAndAnimateAppearance {
contentNode.frame = contentNodeFrame.offsetBy(dx: 0.0, dy: contentNodeFrame.height)
contentNode.alpha = 0.0
}
transition.updateFrame(node: contentNode, frame: contentNodeFrame)
transition.updateAlpha(node: contentNode, alpha: 1.0)
}
if let dismissedContentNode = dismissedContentNode {
var frameCompleted = false
var alphaCompleted = false
let completed = { [weak self, weak dismissedContentNode] in
if let strongSelf = self, let dismissedContentNode = dismissedContentNode, strongSelf.contentNode === dismissedContentNode {
return
}
if frameCompleted && alphaCompleted {
dismissedContentNode?.removeFromSupernode()
}
}
let transitionTargetY = dismissedContentNode.frame.height
transition.updateFrame(node: dismissedContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: transitionTargetY), size: dismissedContentNode.frame.size), completion: { _ in
frameCompleted = true
completed()
})
transition.updateAlpha(node: dismissedContentNode, alpha: 0.0, completion: { _ in
alphaCompleted = true
completed()
})
}
return CGSize(width: width, height: height)
}
}

View File

@ -0,0 +1,368 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import BlurredBackgroundComponent
import BundleIconComponent
import TelegramPresentationData
final class BrowserToolbarComponent: CombinedComponent {
let backgroundColor: UIColor
let separatorColor: UIColor
let textColor: UIColor
let bottomInset: CGFloat
let sideInset: CGFloat
let item: AnyComponentWithIdentity<Empty>?
let collapseFraction: CGFloat
init(
backgroundColor: UIColor,
separatorColor: UIColor,
textColor: UIColor,
bottomInset: CGFloat,
sideInset: CGFloat,
item: AnyComponentWithIdentity<Empty>?,
collapseFraction: CGFloat
) {
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
self.textColor = textColor
self.bottomInset = bottomInset
self.sideInset = sideInset
self.item = item
self.collapseFraction = collapseFraction
}
static func ==(lhs: BrowserToolbarComponent, rhs: BrowserToolbarComponent) -> Bool {
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.separatorColor != rhs.separatorColor {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.bottomInset != rhs.bottomInset {
return false
}
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.item != rhs.item {
return false
}
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
return true
}
static var body: Body {
let background = Child(BlurredBackgroundComponent.self)
let separator = Child(Rectangle.self)
let centerItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
return { context in
let contentHeight: CGFloat = 49.0
let totalHeight = contentHeight + context.component.bottomInset
let offset = context.component.collapseFraction * totalHeight
let size = CGSize(width: context.availableSize.width, height: totalHeight)
let background = background.update(
component: BlurredBackgroundComponent(color: context.component.backgroundColor),
availableSize: CGSize(width: size.width, height: size.height),
transition: context.transition
)
let separator = separator.update(
component: Rectangle(color: context.component.separatorColor, height: UIScreenPixel),
availableSize: CGSize(width: size.width, height: size.height),
transition: context.transition
)
let item = context.component.item.flatMap { item in
return centerItems[item.id].update(
component: item.component,
availableSize: CGSize(width: context.availableSize.width - context.component.sideInset * 2.0, height: contentHeight),
transition: context.transition
)
}
context.add(background
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset))
)
context.add(separator
.position(CGPoint(x: size.width / 2.0, y: 0.0 + offset))
)
if let centerItem = item {
context.add(centerItem
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight / 2.0 + offset))
.appear(Transition.Appear({ _, view, transition in
transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: size.height), to: .zero, additive: true)
}))
.disappear(Transition.Disappear({ view, transition, completion in
transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: size.height), additive: true, completion: { _ in
completion()
})
}))
)
}
return size
}
}
}
final class NavigationToolbarContentComponent: CombinedComponent {
let textColor: UIColor
let canGoBack: Bool
let canGoForward: Bool
let performAction: ActionSlot<BrowserScreen.Action>
init(
textColor: UIColor,
canGoBack: Bool,
canGoForward: Bool,
performAction: ActionSlot<BrowserScreen.Action>
) {
self.textColor = textColor
self.canGoBack = canGoBack
self.canGoForward = canGoForward
self.performAction = performAction
}
static func ==(lhs: NavigationToolbarContentComponent, rhs: NavigationToolbarContentComponent) -> Bool {
if lhs.textColor != rhs.textColor {
return false
}
if lhs.canGoBack != rhs.canGoBack {
return false
}
if lhs.canGoForward != rhs.canGoForward {
return false
}
return true
}
static var body: Body {
let back = Child(Button.self)
let forward = Child(Button.self)
let share = Child(Button.self)
let openIn = Child(Button.self)
return { context in
let availableSize = context.availableSize
let performAction = context.component.performAction
let sideInset: CGFloat = 5.0
let buttonSize = CGSize(width: 50.0, height: availableSize.height)
let spacing = (availableSize.width - buttonSize.width * 4.0 - sideInset * 2.0) / 3.0
let back = back.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Back",
tintColor: context.component.textColor
)
),
isEnabled: context.component.canGoBack,
action: {
performAction.invoke(.navigateBack)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(back
.position(CGPoint(x: sideInset + back.size.width / 2.0, y: availableSize.height / 2.0))
)
let forward = forward.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Instant View/Forward",
tintColor: context.component.textColor
)
),
isEnabled: context.component.canGoForward,
action: {
performAction.invoke(.navigateForward)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(forward
.position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width / 2.0, y: availableSize.height / 2.0))
)
let share = share.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat List/NavigationShare",
tintColor: context.component.textColor
)
),
action: {
performAction.invoke(.share)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(share
.position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width / 2.0, y: availableSize.height / 2.0))
)
let openIn = openIn.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat/Context Menu/Browser",
tintColor: context.component.textColor
)
),
action: {
performAction.invoke(.openIn)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(openIn
.position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0))
)
return availableSize
}
}
}
final class SearchToolbarContentComponent: CombinedComponent {
let strings: PresentationStrings
let textColor: UIColor
let index: Int
let count: Int
let isEmpty: Bool
let performAction: ActionSlot<BrowserScreen.Action>
init(
strings: PresentationStrings,
textColor: UIColor,
index: Int,
count: Int,
isEmpty: Bool,
performAction: ActionSlot<BrowserScreen.Action>
) {
self.strings = strings
self.textColor = textColor
self.index = index
self.count = count
self.isEmpty = isEmpty
self.performAction = performAction
}
static func ==(lhs: SearchToolbarContentComponent, rhs: SearchToolbarContentComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.index != rhs.index {
return false
}
if lhs.count != rhs.count {
return false
}
if lhs.isEmpty != rhs.isEmpty {
return false
}
return true
}
static var body: Body {
let down = Child(Button.self)
let up = Child(Button.self)
let text = Child(Text.self)
return { context in
let availableSize = context.availableSize
let performAction = context.component.performAction
let sideInset: CGFloat = 3.0
let buttonSize = CGSize(width: 50.0, height: availableSize.height)
let down = down.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat/Input/Search/DownButton",
tintColor: context.component.textColor
)
),
isEnabled: context.component.count > 0,
action: {
performAction.invoke(.scrollToNextSearchResult)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(down
.position(CGPoint(x: availableSize.width - sideInset - down.size.width / 2.0, y: availableSize.height / 2.0))
)
let up = up.update(
component: Button(
content: AnyComponent(
BundleIconComponent(
name: "Chat/Input/Search/UpButton",
tintColor: context.component.textColor
)
),
isEnabled: context.component.count > 0,
action: {
performAction.invoke(.scrollToPreviousSearchResult)
}
).minSize(buttonSize),
availableSize: buttonSize,
transition: .easeInOut(duration: 0.2)
)
context.add(up
.position(CGPoint(x: availableSize.width - sideInset - down.size.width + 7.0 - up.size.width / 2.0, y: availableSize.height / 2.0))
)
let currentText: String
if context.component.isEmpty {
currentText = ""
} else if context.component.count == 0 {
currentText = context.component.strings.Conversation_SearchNoResults
} else {
currentText = context.component.strings.Items_NOfM("\(context.component.index + 1)", "\(context.component.count)").string
}
let text = text.update(
component: Text(
text: currentText,
font: Font.regular(15.0),
color: context.component.textColor
),
availableSize: availableSize,
transition: .easeInOut(duration: 0.2)
)
context.add(text
.position(CGPoint(x: availableSize.width - sideInset - down.size.width - up.size.width - text.size.width / 2.0, y: availableSize.height / 2.0))
)
return availableSize
}
}
}

View File

@ -1,103 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import AppBundle
final class BrowserToolbarNavigationContentNode: ASDisplayNode, BrowserToolbarContentNode {
private var theme: BrowserToolbarTheme
private var state: BrowserState
private var interaction: BrowserInteraction?
private let backButton: HighlightableButtonNode
private let forwardButton: HighlightableButtonNode
private let shareButton: HighlightableButtonNode
private let minimizeButton: HighlightableButtonNode
private var validLayout: CGSize?
init(theme: BrowserToolbarTheme, strings: PresentationStrings, state: BrowserState, interaction: BrowserInteraction?) {
self.theme = theme
self.state = state
self.interaction = interaction
self.backButton = HighlightableButtonNode()
self.backButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Back"), color: theme.buttonColor), for: [])
self.backButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Back"), color: theme.disabledButtonColor), for: [.disabled])
self.forwardButton = HighlightableButtonNode()
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Forward"), color: theme.buttonColor), for: [])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Forward"), color: theme.disabledButtonColor), for: [.disabled])
self.shareButton = HighlightableButtonNode()
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationShare"), color: theme.buttonColor), for: [])
self.minimizeButton = HighlightableButtonNode()
self.minimizeButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Minimize"), color: theme.buttonColor), for: [])
super.init()
self.addSubnode(self.backButton)
self.addSubnode(self.forwardButton)
self.addSubnode(self.shareButton)
self.addSubnode(self.minimizeButton)
self.backButton.isEnabled = false
self.forwardButton.isEnabled = false
self.backButton.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside)
self.shareButton.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside)
self.minimizeButton.addTarget(self, action: #selector(self.minimizePressed), forControlEvents: .touchUpInside)
}
func updateState(_ state: BrowserState) {
self.state = state
self.backButton.isEnabled = state.content?.canGoBack ?? false
self.forwardButton.isEnabled = state.content?.canGoForward ?? false
}
func updateTheme(_ theme: BrowserToolbarTheme) {
guard self.theme !== theme else {
return
}
self.theme = theme
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.validLayout == nil
self.validLayout = size
var transition = transition
if isFirstLayout {
transition = .immediate
}
let buttons = [self.backButton, self.forwardButton, self.shareButton, self.minimizeButton]
let sideInset: CGFloat = 5.0
let buttonSize = CGSize(width: 50.0, height: size.height)
let spacing: CGFloat = (size.width - buttonSize.width * CGFloat(buttons.count) - sideInset * 2.0) / CGFloat(buttons.count - 1)
var offset: CGFloat = sideInset
for button in buttons {
transition.updateFrame(node: button, frame: CGRect(origin: CGPoint(x: offset, y: 0.0), size: buttonSize))
offset += buttonSize.width + spacing
}
}
@objc private func backPressed() {
self.interaction?.navigateBack()
}
@objc private func forwardPressed() {
self.interaction?.navigateForward()
}
@objc private func sharePressed() {
self.interaction?.share()
}
@objc private func minimizePressed() {
self.interaction?.minimize()
}
}

View File

@ -1,95 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import AppBundle
final class BrowserToolbarSearchContentNode: ASDisplayNode, BrowserToolbarContentNode {
private var theme: BrowserToolbarTheme
private let strings: PresentationStrings
private var state: BrowserState
private var interaction: BrowserInteraction?
private let upButton: HighlightableButtonNode
private let downButton: HighlightableButtonNode
private let resultsNode: ImmediateTextNode
private var validLayout: CGSize?
init(theme: BrowserToolbarTheme, strings: PresentationStrings, state: BrowserState, interaction: BrowserInteraction?) {
self.theme = theme
self.strings = strings
self.state = state
self.interaction = interaction
self.upButton = HighlightableButtonNode()
self.upButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.buttonColor), for: .normal)
self.upButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.disabledButtonColor), for: .disabled)
self.downButton = HighlightableButtonNode()
self.downButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: theme.buttonColor), for: .normal)
self.downButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: theme.disabledButtonColor), for: .disabled)
self.resultsNode = ImmediateTextNode()
super.init()
self.addSubnode(self.upButton)
self.addSubnode(self.downButton)
self.addSubnode(self.resultsNode)
self.upButton.addTarget(self, action: #selector(self.upPressed), forControlEvents: .touchUpInside)
self.downButton.addTarget(self, action: #selector(self.downPressed), forControlEvents: .touchUpInside)
}
func updateState(_ state: BrowserState) {
self.state = state
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
}
func updateTheme(_ theme: BrowserToolbarTheme) {
guard self.theme !== theme else {
return
}
self.theme = theme
self.upButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.buttonColor), for: .normal)
self.upButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/UpButton"), color: theme.disabledButtonColor), for: .disabled)
self.downButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: theme.buttonColor), for: .normal)
self.downButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: theme.disabledButtonColor), for: .disabled)
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
let buttonSize = CGSize(width: 40.0, height: size.height)
let resultsText: String
if let results = self.state.search?.results {
if results.1 > 0 {
resultsText = self.strings.Items_NOfM("\(results.0 + 1)", "\(results.1)").string
} else {
resultsText = self.strings.Conversation_SearchNoResults
}
} else {
resultsText = ""
}
self.resultsNode.attributedText = NSAttributedString(string: resultsText, font: Font.regular(15.0), textColor: self.theme.buttonColor, paragraphAlignment: .natural)
let resultsSize = self.resultsNode.updateLayout(size)
self.resultsNode.frame = CGRect(origin: CGPoint(x: size.width - 48.0 - 43.0 - resultsSize.width - 12.0, y: floor((size.height - resultsSize.height) / 2.0)), size: resultsSize)
self.downButton.frame = CGRect(origin: CGPoint(x: size.width - 48.0, y: 0.0), size: buttonSize)
self.upButton.frame = CGRect(origin: CGPoint(x: size.width - 48.0 - 43.0, y: 0.0), size: buttonSize)
}
@objc private func upPressed() {
self.interaction?.scrollToPreviousSearchResult()
}
@objc private func downPressed() {
self.interaction?.scrollToNextSearchResult()
}
}

View File

@ -1,17 +1,16 @@
import Foundation
import UIKit
import AsyncDisplayKit
import ComponentFlow
import TelegramCore
import Postbox
import SwiftSignalKit
import Display
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import WebKit
import AppBundle
final class BrowserWebContent: ASDisplayNode, BrowserContent {
final class BrowserWebContent: UIView, BrowserContent, UIScrollViewDelegate {
private let webView: WKWebView
private var _state: BrowserContentState
@ -21,6 +20,8 @@ final class BrowserWebContent: ASDisplayNode, BrowserContent {
return self.statePromise.get()
}
var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in }
init(url: String) {
let configuration = WKWebViewConfiguration()
@ -40,17 +41,24 @@ final class BrowserWebContent: ASDisplayNode, BrowserContent {
title = parsedUrl.host ?? ""
}
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, isInstant: false)
self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, contentType: .webPage)
self.statePromise = Promise<BrowserContentState>(self._state)
super.init()
super.init(frame: .zero)
self.webView.allowsBackForwardNavigationGestures = true
self.webView.scrollView.delegate = self
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.title), options: [], context: nil)
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: [], context: nil)
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: [], context: nil)
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.canGoBack), options: [], context: nil)
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.canGoForward), options: [], context: nil)
self.addSubview(self.webView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
@ -61,12 +69,6 @@ final class BrowserWebContent: ASDisplayNode, BrowserContent {
self.webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.canGoForward))
}
override func didLoad() {
super.didLoad()
self.view.addSubview(self.webView)
}
func setFontSize(_ fontSize: CGFloat) {
let js = "document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='\(Int(fontSize * 100.0))%'"
self.webView.evaluateJavaScript(js, completionHandler: nil)
@ -109,7 +111,7 @@ final class BrowserWebContent: ASDisplayNode, BrowserContent {
})
}
var previousQuery: String?
private var previousQuery: String?
func setSearch(_ query: String?, completion: ((Int) -> Void)?) {
guard self.previousQuery != query else {
return
@ -182,8 +184,15 @@ final class BrowserWebContent: ASDisplayNode, BrowserContent {
self.webView.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.webView.scrollView.contentInset.top), animated: true)
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
transition.updateFrame(view: self.webView, frame: CGRect(origin: CGPoint(x: 0.0, y: 56.0), size: CGSize(width: size.width, height: size.height - 56.0)))
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: Transition) {
var scrollInsets = insets
scrollInsets.top = 0.0
if self.webView.scrollView.contentInset != insets {
self.webView.scrollView.contentInset = scrollInsets
self.webView.scrollView.scrollIndicatorInsets = scrollInsets
}
self.previousScrollingOffset = ScrollingOffsetState(value: self.webView.scrollView.contentOffset.y, isDraggingOrDecelerating: self.webView.scrollView.isDragging || self.webView.scrollView.isDecelerating)
transition.setFrame(view: self.webView, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top)))
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
@ -195,15 +204,63 @@ final class BrowserWebContent: ASDisplayNode, BrowserContent {
if keyPath == "title" {
updateState { $0.withUpdatedTitle(self.webView.title ?? "") }
} else if keyPath == "url" {
} else if keyPath == "URL" {
updateState { $0.withUpdatedUrl(self.webView.url?.absoluteString ?? "") }
self.didSetupSearch = false
} else if keyPath == "estimatedProgress" {
updateState { $0.withUpdatedEstimatedProgress(self.webView.estimatedProgress) }
} else if keyPath == "canGoBack" {
updateState { $0.withUpdatedCanGoBack(self.webView.canGoBack) }
self.webView.disablesInteractiveTransitionGestureRecognizer = self.webView.canGoBack
} else if keyPath == "canGoForward" {
updateState { $0.withUpdatedCanGoForward(self.webView.canGoForward) }
}
}
private struct ScrollingOffsetState: Equatable {
var value: CGFloat
var isDraggingOrDecelerating: Bool
}
private var previousScrollingOffset: ScrollingOffsetState?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrollingOffset(isReset: false, transition: .immediate)
}
private func snapScrollingOffsetToInsets() {
let transition = Transition(animation: .curve(duration: 0.4, curve: .spring))
self.updateScrollingOffset(isReset: false, transition: transition)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.snapScrollingOffsetToInsets()
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.snapScrollingOffsetToInsets()
}
private func updateScrollingOffset(isReset: Bool, transition: Transition) {
let scrollView = self.webView.scrollView
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
if let previousScrollingOffsetValue = self.previousScrollingOffset {
let currentBounds = scrollView.bounds
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY)
let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value
self.onScrollingUpdate(ContentScrollingUpdate(
relativeOffset: relativeOffset,
absoluteOffsetToTopEdge: offsetToTopEdge,
absoluteOffsetToBottomEdge: offsetToBottomEdge,
isReset: isReset,
isInteracting: isInteracting,
transition: transition
))
}
self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting)
}
}

View File

@ -899,18 +899,26 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
return settings
}
var isCompact = false
if let metrics = navigationController?.validLayout?.metrics, case .compact = metrics.widthClass {
isCompact = true
}
let _ = (settings
|> deliverOnMainQueue).start(next: { settings in
if settings.defaultWebBrowser == nil {
// let controller = BrowserScreen(context: context, subject: .webPage(parsedUrl.absoluteString))
// navigationController?.pushViewController(controller)
if let window = navigationController?.view.window {
let controller = SFSafariViewController(url: parsedUrl)
controller.preferredBarTintColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
controller.preferredControlTintColor = presentationData.theme.rootController.navigationBar.accentTextColor
window.rootViewController?.present(controller, animated: true)
if isCompact {
let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString))
navigationController?.pushViewController(controller)
} else {
context.sharedContext.applicationBindings.openUrl(parsedUrl.absoluteString)
if let window = navigationController?.view.window {
let controller = SFSafariViewController(url: parsedUrl)
controller.preferredBarTintColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
controller.preferredControlTintColor = presentationData.theme.rootController.navigationBar.accentTextColor
window.rootViewController?.present(controller, animated: true)
} else {
context.sharedContext.applicationBindings.openUrl(parsedUrl.absoluteString)
}
}
} else {
let openInOptions = availableOpenInOptions(context: context, item: .url(url: url))