Instant View improvements

This commit is contained in:
Ilya Laktyushin 2018-11-21 01:04:04 +04:00
parent 431e7082c2
commit f1e7c5ac35
15 changed files with 426 additions and 83 deletions

View File

@ -36,6 +36,8 @@
0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */; };
0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */; };
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */; };
09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */; };
09619B9621A4ABF600493558 /* InstantPageReferenceControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */; };
096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; };
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
@ -1096,6 +1098,8 @@
0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageFeedbackNode.swift; sourceTree = "<group>"; };
0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsNode.swift; sourceTree = "<group>"; };
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageScrollableNode.swift; sourceTree = "<group>"; };
09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageReferenceController.swift; sourceTree = "<group>"; };
09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageReferenceControllerNode.swift; sourceTree = "<group>"; };
096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = "<group>"; };
096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = "<group>"; };
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; };
@ -3075,6 +3079,8 @@
D07827CC1E03F32C00071108 /* Instant Page */ = {
isa = PBXGroup;
children = (
09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */,
09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */,
D0215D371E040F53001A0B1E /* InstantPageNode.swift */,
D0215D391E041003001A0B1E /* InstantPageLayout.swift */,
D0215D3B1E041014001A0B1E /* InstantPageItem.swift */,
@ -5012,6 +5018,7 @@
D0EC6CF01EB9F58800EBF1C3 /* AutomaticMediaDownloadSettings.swift in Sources */,
D0EC6CF11EB9F58800EBF1C3 /* GeneratedMediaStoreSettings.swift in Sources */,
D0EC6CF21EB9F58800EBF1C3 /* VoiceCallSettings.swift in Sources */,
09619B9621A4ABF600493558 /* InstantPageReferenceControllerNode.swift in Sources */,
D0F8C397201774A200236FC5 /* FeedGroupingController.swift in Sources */,
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
@ -5527,6 +5534,7 @@
D0EC6DFE1EB9F58900EBF1C3 /* GalleryControllerPresentationState.swift in Sources */,
D0E8B8BB2044780600605593 /* ItemListSecretChatKeyItem.swift in Sources */,
D0EC6DFF1EB9F58900EBF1C3 /* GalleryItem.swift in Sources */,
09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */,
D0EC6E001EB9F58900EBF1C3 /* GalleryItemNode.swift in Sources */,
D048EA8B1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift in Sources */,
D0EC6E011EB9F58900EBF1C3 /* GalleryPagerNode.swift in Sources */,

View File

@ -60,7 +60,7 @@ func imageHasTransparency(_ cgImage: CGImage) -> Bool {
return false
}
private func scaledContext(_ cgImage: CGImage, maxSize: CGSize) -> DrawingContext {
private func scaledDrawingContext(_ cgImage: CGImage, maxSize: CGSize) -> DrawingContext {
var size = CGSize(width: cgImage.width, height: cgImage.height)
if (size.width > maxSize.width && size.height > maxSize.height) {
size = size.aspectFilled(maxSize)
@ -80,27 +80,28 @@ func imageRequiresInversion(_ cgImage: CGImage) -> Bool {
return false
}
let context = scaledContext(cgImage, maxSize: CGSize(width: 128.0, height: 128.0))
let context = scaledDrawingContext(cgImage, maxSize: CGSize(width: 128.0, height: 128.0))
if let cgImage = context.generateImage()?.cgImage, let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) {
var hasAlpha = false
for i in 0 ..< 255 {
if histogramBins[alphaBinIndex][i] > 0 {
hasAlpha = true
break
}
}
guard hasAlpha else {
return false
}
if hasAlpha {
var matching: Int = 0
var total: Int = 0
for y in 0 ..< Int(context.size.height) {
for x in 0 ..< Int(context.size.width) {
var hue: CGFloat = 0.0
var saturation: CGFloat = 0.0
var brightness: CGFloat = 0.0
var alpha: CGFloat = 0.0
context.colorAt(CGPoint(x: x, y: y)).getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
if context.colorAt(CGPoint(x: x, y: y)).getHue(nil, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
if alpha < 1.0 {
hasAlpha = true
}
if alpha > 0.0 {
total += 1
@ -110,7 +111,9 @@ func imageRequiresInversion(_ cgImage: CGImage) -> Bool {
}
}
}
}
return CGFloat(matching) / CGFloat(total) > 0.85
}
}
return false
}

View File

@ -83,7 +83,15 @@ final class InstantPageController: ViewController {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)))
}
}, navigateBack: { [weak self] in
self?.navigationController?.popViewController(animated: true)
if let strongSelf = self, let controllers = strongSelf.navigationController?.viewControllers.reversed() {
for controller in controllers {
if !(controller is InstantPageController) {
strongSelf.navigationController?.popToViewController(controller, animated: true)
return
}
}
strongSelf.navigationController?.popViewController(animated: true)
}
})
self.displayNodeDidLoad()

View File

@ -258,7 +258,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.setupScrollOffsetOnLayout = self.webPage == nil
self.webPage = webPage
self.initialAnchor = anchor
self.initialAnchor = anchor?.removingPercentEncoding
self.currentLayout = nil
self.updateLayout()
@ -483,9 +483,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] in
itemNode.requestLayoutUpdate = { [weak self] animated in
if let strongSelf = self {
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: true)
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: animated)
}
}
@ -526,9 +526,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if self.visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
tileNode.frame = tileFrame
// if case let .animated(duration, _) = transition {
// tileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
// }
if let topNode = topNode {
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
@ -793,7 +790,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if let currentLayout = self.currentLayout {
for item in currentLayout.items {
let itemFrame = self.effectiveFrameForItem(item).insetBy(dx: -2.0, dy: -2.0)
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))
@ -843,8 +840,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
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()
@ -924,24 +925,31 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat)? {
private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat, [InstantPageDetailsItem])? {
for item in items {
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
return (item, 0.0)
return (item, 0.0, [])
} else if let item = item as? InstantPageTextItem {
if let lineIndex = item.anchors[anchor] {
return (item, item.lines[lineIndex].frame.minY - 10.0)
return (item, item.lines[lineIndex].frame.minY - 10.0, [])
}
}
else if let item = item as? InstantPageDetailsItem {
if let anchorItem = findAnchorItem(anchor, items: item.items) {
return anchorItem
if let (foundItem, offset, detailsItems) = self.findAnchorItem(anchor, items: item.items) {
var detailsItems = detailsItems
detailsItems.insert(item, at: 0)
return (foundItem, offset, detailsItems)
}
}
}
return nil
}
private func presentReferenceView(item: InstantPageTextItem) {
let controller = InstantPageReferenceController(account: self.account, item: item)
self.present(controller, nil)
}
private func openUrl(_ url: InstantPageUrlItem) {
guard let items = self.currentLayout?.items else {
return
@ -950,15 +958,43 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var baseUrl = url.url
var anchor: String?
if let anchorRange = url.url.range(of: "#") {
anchor = String(baseUrl[anchorRange.upperBound...])
anchor = String(baseUrl[anchorRange.upperBound...]).removingPercentEncoding
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
}
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl, let anchor = anchor {
if !anchor.isEmpty {
if let (item, offset) = findAnchorItem(String(anchor), items: items) {
let frame = effectiveFrameForItem(item)
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: frame.minY + offset - self.scrollNode.view.contentInset.top), animated: true)
if let (item, lineOffset, detailsItems) = findAnchorItem(String(anchor), items: items) {
var previousDetailsItem: InstantPageDetailsItem?
var previousDetailsNode: InstantPageDetailsNode?
var containerOffset: CGFloat = 0.0
for detailsItem in detailsItems {
if let previousNode = previousDetailsNode, let previousDetailsItem = previousDetailsItem {
previousNode.contentNode.updateDetailsExpanded(detailsItem.index, true, animated: false)
let frame = previousNode.contentNode.effectiveFrameForItem(detailsItem)
containerOffset += frame.minY + previousDetailsItem.titleHeight
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)
previousDetailsItem = detailsItem
}
}
let frame: CGRect
if let previousDetailsNode = previousDetailsNode, let previousDetailsItem = previousDetailsItem {
containerOffset += previousDetailsItem.titleHeight
frame = previousDetailsNode.contentNode.effectiveFrameForItem(item)
} else {
frame = self.effectiveFrameForItem(item)
}
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: containerOffset + frame.minY + lineOffset - self.scrollNode.view.contentInset.top), animated: true)
}
} else {
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top), animated: true)
@ -1034,6 +1070,18 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}))
}
private func openUrlIn(_ url: InstantPageUrlItem) {
if let applicationContext = self.account.applicationContext as? TelegramApplicationContext {
let presentationData = applicationContext.currentPresentationData.with { $0 }
let actionSheet = OpenInActionSheetController(postbox: self.account.postbox, applicationContext: applicationContext, theme: presentationData.theme, strings: presentationData.strings, item: .url(url: url.url), openUrl: { [weak self] url in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: {})
}
})
self.present(actionSheet, nil)
}
}
private func mediasFromItems(_ items: [InstantPageItem]) -> [InstantPageMedia] {
var medias: [InstantPageMedia] = []
for item in items {
@ -1138,12 +1186,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
private func updateDetailsExpanded(_ index: Int, _ expanded: Bool) {
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: true)
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: animated)
}
private func presentSettings() {

View File

@ -28,7 +28,7 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = []
var requestLayoutUpdate: (() -> Void)?
var requestLayoutUpdate: ((Bool) -> Void)?
var currentLayout: InstantPageLayout
let contentSize: CGSize
@ -201,8 +201,8 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] in
self?.requestLayoutUpdate?()
itemNode.requestLayoutUpdate = { [weak self] animated in
self?.requestLayoutUpdate?(animated)
}
}
}
@ -297,12 +297,12 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
// }
}
private func updateDetailsExpanded(_ index: Int, _ expanded: Bool) {
func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) {
if var currentExpandedDetails = self.currentExpandedDetails {
currentExpandedDetails[index] = expanded
self.currentExpandedDetails = currentExpandedDetails
}
self.requestLayoutUpdate?()
self.requestLayoutUpdate?(animated)
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
@ -331,7 +331,7 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
return contentOffset
}
private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
for (_, itemNode) in self.visibleItemsWithNodes {
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
return detailsNode
@ -361,7 +361,7 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
return CGRect(origin: origin, size: tile.frame.size)
}
private func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
let layoutOrigin = item.frame.origin
var origin = layoutOrigin
@ -454,7 +454,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
var previousNode: InstantPageDetailsNode?
var requestLayoutUpdate: (() -> Void)?
var requestLayoutUpdate: ((Bool) -> Void)?
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
self.account = account
@ -522,8 +522,8 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
}
}
self.contentNode.requestLayoutUpdate = { [weak self] in
self?.requestLayoutUpdate?()
self.contentNode.requestLayoutUpdate = { [weak self] animated in
self?.requestLayoutUpdate?(animated)
}
self.update(strings: strings, theme: theme)
@ -531,12 +531,12 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
@objc func buttonPressed() {
self.setExpanded(!self.expanded, animated: true)
self.updateExpanded(expanded)
}
func setExpanded(_ expanded: Bool, animated: Bool) {
self.expanded = expanded
self.arrowNode.setOpen(expanded, animated: animated)
self.updateExpanded(expanded)
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {

View File

@ -30,7 +30,7 @@ final private class InstantPageProgressNode: ASDisplayNode {
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.5, curve: .spring)
transition = .animated(duration: 0.7, curve: .spring)
} else {
transition = .immediate
}

View File

@ -25,7 +25,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, media: self.media, interactive: self.interactive, openMedia: openMedia)
return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia)
}
func matchesAnchor(_ anchor: String) -> Bool {

View File

@ -19,13 +19,13 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode {
private var localIsVisible = false
init(account: Account, webPage: TelegramMediaWebpage, media: InstantPageMedia, interactive: Bool, openMedia: @escaping (InstantPageMedia) -> Void) {
init(account: Account, webPage: TelegramMediaWebpage, theme: InstantPageTheme, media: InstantPageMedia, interactive: Bool, openMedia: @escaping (InstantPageMedia) -> Void) {
self.account = account
self.media = media
self.interactive = interactive
self.openMedia = openMedia
self.videoNode = UniversalVideoNode(postbox: account.postbox, audioSession: account.telegramApplicationContext.mediaManager!.audioSession, manager: account.telegramApplicationContext.mediaManager!.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), loopVideo: true, enableSound: false, fetchAutomatically: true), priority: .embedded, autoplay: true)
self.videoNode = UniversalVideoNode(postbox: account.postbox, audioSession: account.telegramApplicationContext.mediaManager!.audioSession, manager: account.telegramApplicationContext.mediaManager!.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor), priority: .embedded, autoplay: true)
super.init()

View File

@ -0,0 +1,65 @@
import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
final class InstantPageReferenceController: ViewController {
private var controllerNode: InstantPageReferenceControllerNode {
return self.displayNode as! InstantPageReferenceControllerNode
}
private var animatedIn = false
private let account: Account
private let item: InstantPageTextItem
init(account: Account, item: InstantPageTextItem) {
self.account = account
self.item = item
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func loadDisplayNode() {
self.displayNode = InstantPageReferenceControllerNode(account: self.account, item: self.item)
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.controllerNode.close = { [weak self] in
self?.dismiss()
}
}
override public func loadView() {
super.loadView()
self.statusBar.removeFromSupernode()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn()
}
}
override public func dismiss(completion: (() -> Void)? = nil) {
self.controllerNode.animateOut(completion: completion)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
}

View File

@ -0,0 +1,180 @@
import Foundation
import Display
import AsyncDisplayKit
import TelegramCore
class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private var presentationData: PresentationData
private var containerLayout: (ContainerViewLayout, CGFloat)?
private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode
private let contentContainerNode: ASDisplayNode
private let contentBackgroundNode: ASImageNode
private let titleNode: ASTextNode
private let separatorNode: ASDisplayNode
private let closeButton: HighlightableButtonNode
var dismiss: (() -> Void)?
var close: (() -> Void)?
init(account: Account, item: InstantPageTextItem) {
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.wrappingScrollNode = ASScrollNode()
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
self.contentContainerNode = ASDisplayNode()
self.contentContainerNode.isOpaque = false
self.contentContainerNode.clipsToBounds = true
self.contentBackgroundNode = ASImageNode()
self.contentBackgroundNode.displaysAsynchronously = false
self.contentBackgroundNode.displayWithoutProcessing = true
self.contentBackgroundNode.image = roundedBackground
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.ShareMenu_ShareTo, font: Font.medium(20.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemSeparatorColor
self.closeButton = HighlightableButtonNode()
super.init()
self.backgroundColor = nil
self.isOpaque = false
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
self.addSubnode(self.dimNode)
self.wrappingScrollNode.view.delegate = self
self.addSubnode(self.wrappingScrollNode)
self.wrappingScrollNode.addSubnode(self.contentBackgroundNode)
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
}
override func didLoad() {
super.didLoad()
if #available(iOSApplicationExtension 11.0, *) {
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
}
}
@objc func closeButtonPressed() {
self.close?()
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.closeButtonPressed()
}
}
func animateIn() {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}
func animateOut(completion: (() -> Void)? = nil) {
var dimCompleted = false
var offsetCompleted = false
let internalCompletion: () -> Void = { [weak self] in
if let strongSelf = self, dimCompleted && offsetCompleted {
strongSelf.dismiss?()
}
completion?()
}
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
dimCompleted = true
internalCompletion()
})
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
offsetCompleted = true
internalCompletion()
})
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.contains(point) {
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) {
return self.dimNode.view
}
}
return super.hitTest(point, with: event)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let contentOffset = scrollView.contentOffset
let additionalTopHeight = max(0.0, -contentOffset.y)
if additionalTopHeight >= 30.0 {
self.closeButtonPressed()
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [.statusBar, .input])
let cleanInsets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top)
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
if insets.bottom > 0 {
bottomInset -= 12.0
}
let titleAreaHeight: CGFloat = 54.0
let buttonHeight: CGFloat = 57.0
let sectionSpacing: CGFloat = 8.0
let maximumContentHeight = layout.size.height - insets.top - max(bottomInset + buttonHeight, insets.bottom) - sectionSpacing
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
let sideInset = floor((layout.size.width - width) / 2.0)
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
let contentFrame = contentContainerFrame.insetBy(dx: 0.0, dy: 0.0)
self.containerLayout = (layout, navigationBarHeight)
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleAreaHeight))
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: 15.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
//transition.updateFrame(node: self.closeButtonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: width, height: buttonHeight)))
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
}
}

View File

@ -288,12 +288,12 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
var minCellWidth: CGFloat = 1.0
var maxCellWidth: CGFloat = 1.0
if let text = cell.text {
if let shortestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage, minimizeWidth: true).0 {
if let shortestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidthLimit - totalCellPadding), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage, minimizeWidth: true).0 {
minCellWidth = shortestTextItem.effectiveWidth() + totalCellPadding
}
if let longestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage).0 {
maxCellWidth = longestTextItem.effectiveWidth() + totalCellPadding
if let longestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidthLimit - totalCellPadding), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage).0 {
maxCellWidth = max(minCellWidth, longestTextItem.effectiveWidth() + totalCellPadding)
}
}
if cell.colspan > 1 {
@ -369,7 +369,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
let delta = minSpanWidth - minWidth
for i in range {
if let columnWidth = minColumnWidths[i] {
let growth = round(delta / CGFloat(range.count))
let growth = floor(delta / CGFloat(range.count))
minColumnWidths[i] = columnWidth + growth
availableWidth -= growth
}
@ -441,7 +441,6 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
origin.x += width
}
}
k += cell.colspan
} else {
break
@ -462,7 +461,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
var additionalItems: [InstantPageItem] = []
var cellHeight: CGFloat?
if let text = cell.text {
let (textItem, items, _) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidth - totalCellPadding, alignment: cell.alignment.textAlignment, offset: CGPoint(), media: media, webpage: webpage)
let (textItem, items, _) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidth - totalCellPadding), boundingWidth: cellWidth - totalCellPadding, alignment: cell.alignment.textAlignment, offset: CGPoint(), media: media, webpage: webpage)
if let textItem = textItem {
isEmptyRow = false
textItem.frame = textItem.frame.offsetBy(dx: tableCellInsets.left, dy: 0.0)

View File

@ -119,7 +119,6 @@ final class InstantPageTextItem: InstantPageItem {
for i in 0 ..< self.lines.count {
let line = self.lines[i]
let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment)
if lineFrame.maxY < upperOriginBound || lineFrame.minY > lowerOriginBound {
continue
@ -131,10 +130,11 @@ final class InstantPageTextItem: InstantPageItem {
if !line.markedItems.isEmpty {
context.saveGState()
for item in line.markedItems {
let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0)
context.setFillColor(item.color.cgColor)
let height = floor(item.frame.size.height * 2.2)
let rect = CGRect(x: item.frame.minX - 2.0, y: floor(item.frame.minY + (item.frame.height - height) / 2.0), width: item.frame.width + 4.0, height: height)
let rect = CGRect(x: itemFrame.minX - 2.0, y: floor(itemFrame.minY + (itemFrame.height - height) / 2.0), width: itemFrame.width + 4.0, height: height)
let path = UIBezierPath.init(roundedRect: rect, cornerRadius: 3.0)
context.addPath(path.cgPath)
context.fillPath()
@ -146,7 +146,8 @@ final class InstantPageTextItem: InstantPageItem {
if !line.strikethroughItems.isEmpty {
for item in line.strikethroughItems {
context.fill(CGRect(x: item.frame.minX, y: item.frame.minY + floor((lineFrame.size.height / 2.0) + 1.0), width: item.frame.size.width, height: 1.0))
let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0)
context.fill(CGRect(x: itemFrame.minX, y: itemFrame.minY + floor((lineFrame.size.height / 2.0) + 1.0), width: itemFrame.size.width, height: 1.0))
}
}
}
@ -401,7 +402,7 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem {
}
}
func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil) -> NSAttributedString {
func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil, boundingWidth: CGFloat? = nil) -> NSAttributedString {
switch text {
case .empty:
return NSAttributedString(string: "", attributes: styleStack.textAttributes())
@ -451,7 +452,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
case let .concat(texts):
let string = NSMutableAttributedString()
for text in texts {
let substring = attributedStringForRichText(text, styleStack: styleStack, url: url)
let substring = attributedStringForRichText(text, styleStack: styleStack, url: url, boundingWidth: boundingWidth)
string.append(substring)
}
return string
@ -483,6 +484,11 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
let descent: CGFloat
let width: CGFloat
}
var dimensions = dimensions
if let boundingWidth = boundingWidth {
dimensions = dimensions.fittedToWidthOrSmaller(boundingWidth)
}
let extentBuffer = UnsafeMutablePointer<RunStruct>.allocate(capacity: 1)
extentBuffer.initialize(to: RunStruct(ascent: 0.0, descent: 0.0, width: dimensions.width))
var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in
@ -581,6 +587,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
var line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0)
var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
let lineRange = NSMakeRange(lastIndex, lineCharacterCount)
let substring = string.attributedSubstring(from: lineRange).string
var stop = false
if maxNumberOfLines > 0 && lines.count == maxNumberOfLines - 1 && lastIndex + lineCharacterCount < string.length {
@ -634,7 +641,11 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
}
}
if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth + 1.0, let imageItem = lineImageItems.last {
if substring.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && lineImageItems.count > 0 {
extraDescent += max(6.0, fontLineSpacing / 2.0)
}
if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth + 5.0, let imageItem = lineImageItems.last {
indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound)
continue
}

View File

@ -2559,19 +2559,40 @@ func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumA
})
}
var remoteArtworkData: Signal<(Data?, Data?, Bool), NoError> = .single((nil, nil, false))
if let albumArt = albumArt {
var immediateArtworkData: Signal<(Data?, Data?, Bool), NoError> = .single((nil, nil, false))
if let fileReference = fileReference, let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
let thumbnailResource = smallestRepresentation.resource
let fetchedThumbnail = fetchedMediaResource(postbox: postbox, reference: fileReference.resourceReference(thumbnailResource))
let thumbnail = Signal<Data?, NoError> { subscriber in
let fetchedDisposable = fetchedThumbnail.start()
let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource).start(next: { next in
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
}, error: subscriber.putError, completed: subscriber.putCompletion)
return ActionDisposable {
fetchedDisposable.dispose()
thumbnailDisposable.dispose()
}
}
immediateArtworkData = thumbnail
|> map { thumbnailData in
return (thumbnailData, nil, false)
}
} else if let albumArt = albumArt {
if thumbnail {
remoteArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource)
immediateArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource)
|> map { thumbnailData in
return (thumbnailData, nil, false)
}
} else {
remoteArtworkData = albumArtFullSizeDatas(postbox: postbox, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource)
immediateArtworkData = albumArtFullSizeDatas(postbox: postbox, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource)
}
}
return combineLatest(fileArtworkData, remoteArtworkData)
return combineLatest(fileArtworkData, immediateArtworkData)
|> map { fileArtworkData, remoteArtworkData in
let remoteThumbnailData = remoteArtworkData.0
let remoteFullSizeData = remoteArtworkData.1