mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 11:25:38 +00:00
Instant View improvements
This commit is contained in:
parent
431e7082c2
commit
f1e7c5ac35
@ -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 */,
|
||||
|
||||
@ -1971,7 +1971,7 @@ func handlePeerInfoAboutTextAction(account: Account, peerId: PeerId, navigateDis
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
])])
|
||||
controller.present(actionSheet, in: .window(.root))
|
||||
case let .mention(mention):
|
||||
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
|
||||
|
||||
@ -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,37 +80,40 @@ 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
|
||||
}
|
||||
|
||||
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 alpha > 0.0 {
|
||||
total += 1
|
||||
if saturation < 0.1 && brightness < 0.25 {
|
||||
matching += 1
|
||||
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 saturation: CGFloat = 0.0
|
||||
var brightness: CGFloat = 0.0
|
||||
var alpha: CGFloat = 0.0
|
||||
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
|
||||
if saturation < 0.1 && brightness < 0.25 {
|
||||
matching += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return CGFloat(matching) / CGFloat(total) > 0.85
|
||||
}
|
||||
return CGFloat(matching) / CGFloat(total) > 0.85
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,7 +840,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.openUrl(url)
|
||||
if canOpenIn {
|
||||
strongSelf.openUrlIn(url)
|
||||
} else {
|
||||
strongSelf.openUrl(url)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ActionSheetButtonItem(title: self.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
|
||||
@ -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() {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
65
TelegramUI/InstantPageReferenceController.swift
Normal file
65
TelegramUI/InstantPageReferenceController.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
180
TelegramUI/InstantPageReferenceControllerNode.swift
Normal file
180
TelegramUI/InstantPageReferenceControllerNode.swift
Normal 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)))
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -151,16 +151,16 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
|
||||
let previousItems = Atomic<[SharePeerEntry]?>(value: [])
|
||||
self.disposable.set((items
|
||||
|> deliverOnMainQueue).start(next: { [weak self] entries in
|
||||
if let strongSelf = self {
|
||||
let previousEntries = previousItems.swap(entries)
|
||||
strongSelf.entries = entries
|
||||
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = preparedGridEntryTransition(account: account, from: previousEntries ?? [], to: entries, interfaceInteraction: controllerInteraction)
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
}))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] entries in
|
||||
if let strongSelf = self {
|
||||
let previousEntries = previousItems.swap(entries)
|
||||
strongSelf.entries = entries
|
||||
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = preparedGridEntryTransition(account: account, from: previousEntries ?? [], to: entries, interfaceInteraction: controllerInteraction)
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
}))
|
||||
|
||||
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user