mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-06 14:25:04 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/TelegramUI
This commit is contained in:
commit
f1acc28544
@ -36,6 +36,8 @@
|
|||||||
0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */; };
|
0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */; };
|
||||||
0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */; };
|
0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */; };
|
||||||
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B8D21A34C0100493558 /* InstantPageScrollableNode.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 */; };
|
096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; };
|
||||||
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
|
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
|
||||||
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; };
|
||||||
@ -3075,6 +3079,8 @@
|
|||||||
D07827CC1E03F32C00071108 /* Instant Page */ = {
|
D07827CC1E03F32C00071108 /* Instant Page */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
09619B9321A4ABF500493558 /* InstantPageReferenceController.swift */,
|
||||||
|
09619B9421A4ABF600493558 /* InstantPageReferenceControllerNode.swift */,
|
||||||
D0215D371E040F53001A0B1E /* InstantPageNode.swift */,
|
D0215D371E040F53001A0B1E /* InstantPageNode.swift */,
|
||||||
D0215D391E041003001A0B1E /* InstantPageLayout.swift */,
|
D0215D391E041003001A0B1E /* InstantPageLayout.swift */,
|
||||||
D0215D3B1E041014001A0B1E /* InstantPageItem.swift */,
|
D0215D3B1E041014001A0B1E /* InstantPageItem.swift */,
|
||||||
@ -5012,6 +5018,7 @@
|
|||||||
D0EC6CF01EB9F58800EBF1C3 /* AutomaticMediaDownloadSettings.swift in Sources */,
|
D0EC6CF01EB9F58800EBF1C3 /* AutomaticMediaDownloadSettings.swift in Sources */,
|
||||||
D0EC6CF11EB9F58800EBF1C3 /* GeneratedMediaStoreSettings.swift in Sources */,
|
D0EC6CF11EB9F58800EBF1C3 /* GeneratedMediaStoreSettings.swift in Sources */,
|
||||||
D0EC6CF21EB9F58800EBF1C3 /* VoiceCallSettings.swift in Sources */,
|
D0EC6CF21EB9F58800EBF1C3 /* VoiceCallSettings.swift in Sources */,
|
||||||
|
09619B9621A4ABF600493558 /* InstantPageReferenceControllerNode.swift in Sources */,
|
||||||
D0F8C397201774A200236FC5 /* FeedGroupingController.swift in Sources */,
|
D0F8C397201774A200236FC5 /* FeedGroupingController.swift in Sources */,
|
||||||
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
|
D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */,
|
||||||
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
|
D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */,
|
||||||
@ -5527,6 +5534,7 @@
|
|||||||
D0EC6DFE1EB9F58900EBF1C3 /* GalleryControllerPresentationState.swift in Sources */,
|
D0EC6DFE1EB9F58900EBF1C3 /* GalleryControllerPresentationState.swift in Sources */,
|
||||||
D0E8B8BB2044780600605593 /* ItemListSecretChatKeyItem.swift in Sources */,
|
D0E8B8BB2044780600605593 /* ItemListSecretChatKeyItem.swift in Sources */,
|
||||||
D0EC6DFF1EB9F58900EBF1C3 /* GalleryItem.swift in Sources */,
|
D0EC6DFF1EB9F58900EBF1C3 /* GalleryItem.swift in Sources */,
|
||||||
|
09619B9521A4ABF600493558 /* InstantPageReferenceController.swift in Sources */,
|
||||||
D0EC6E001EB9F58900EBF1C3 /* GalleryItemNode.swift in Sources */,
|
D0EC6E001EB9F58900EBF1C3 /* GalleryItemNode.swift in Sources */,
|
||||||
D048EA8B1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift in Sources */,
|
D048EA8B1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift in Sources */,
|
||||||
D0EC6E011EB9F58900EBF1C3 /* GalleryPagerNode.swift in Sources */,
|
D0EC6E011EB9F58900EBF1C3 /* GalleryPagerNode.swift in Sources */,
|
||||||
|
|||||||
@ -60,7 +60,7 @@ func imageHasTransparency(_ cgImage: CGImage) -> Bool {
|
|||||||
return false
|
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)
|
var size = CGSize(width: cgImage.width, height: cgImage.height)
|
||||||
if (size.width > maxSize.width && size.height > maxSize.height) {
|
if (size.width > maxSize.width && size.height > maxSize.height) {
|
||||||
size = size.aspectFilled(maxSize)
|
size = size.aspectFilled(maxSize)
|
||||||
@ -80,27 +80,28 @@ func imageRequiresInversion(_ cgImage: CGImage) -> Bool {
|
|||||||
return false
|
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) {
|
if let cgImage = context.generateImage()?.cgImage, let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) {
|
||||||
var hasAlpha = false
|
var hasAlpha = false
|
||||||
for i in 0 ..< 255 {
|
for i in 0 ..< 255 {
|
||||||
if histogramBins[alphaBinIndex][i] > 0 {
|
if histogramBins[alphaBinIndex][i] > 0 {
|
||||||
hasAlpha = true
|
hasAlpha = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard hasAlpha else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if hasAlpha {
|
||||||
var matching: Int = 0
|
var matching: Int = 0
|
||||||
var total: Int = 0
|
var total: Int = 0
|
||||||
for y in 0 ..< Int(context.size.height) {
|
for y in 0 ..< Int(context.size.height) {
|
||||||
for x in 0 ..< Int(context.size.width) {
|
for x in 0 ..< Int(context.size.width) {
|
||||||
var hue: CGFloat = 0.0
|
|
||||||
var saturation: CGFloat = 0.0
|
var saturation: CGFloat = 0.0
|
||||||
var brightness: CGFloat = 0.0
|
var brightness: CGFloat = 0.0
|
||||||
var alpha: 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 {
|
if alpha > 0.0 {
|
||||||
total += 1
|
total += 1
|
||||||
@ -110,7 +111,9 @@ func imageRequiresInversion(_ cgImage: CGImage) -> Bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return CGFloat(matching) / CGFloat(total) > 0.85
|
return CGFloat(matching) / CGFloat(total) > 0.85
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,15 @@ final class InstantPageController: ViewController {
|
|||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)))
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)))
|
||||||
}
|
}
|
||||||
}, navigateBack: { [weak self] in
|
}, 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()
|
self.displayNodeDidLoad()
|
||||||
|
|||||||
@ -258,7 +258,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.setupScrollOffsetOnLayout = self.webPage == nil
|
self.setupScrollOffsetOnLayout = self.webPage == nil
|
||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
self.initialAnchor = anchor
|
self.initialAnchor = anchor?.removingPercentEncoding
|
||||||
|
|
||||||
self.currentLayout = nil
|
self.currentLayout = nil
|
||||||
self.updateLayout()
|
self.updateLayout()
|
||||||
@ -483,9 +483,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
itemNode = newNode
|
itemNode = newNode
|
||||||
|
|
||||||
if let itemNode = itemNode as? InstantPageDetailsNode {
|
if let itemNode = itemNode as? InstantPageDetailsNode {
|
||||||
itemNode.requestLayoutUpdate = { [weak self] in
|
itemNode.requestLayoutUpdate = { [weak self] animated in
|
||||||
if let strongSelf = self {
|
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 {
|
if self.visibleTiles[tileIndex] == nil {
|
||||||
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
|
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
|
||||||
tileNode.frame = tileFrame
|
tileNode.frame = tileFrame
|
||||||
// if case let .animated(duration, _) = transition {
|
|
||||||
// tileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
|
||||||
// }
|
|
||||||
if let topNode = topNode {
|
if let topNode = topNode {
|
||||||
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
|
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
|
||||||
} else {
|
} else {
|
||||||
@ -793,7 +790,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
|
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
|
||||||
if let currentLayout = self.currentLayout {
|
if let currentLayout = self.currentLayout {
|
||||||
for item in currentLayout.items {
|
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 itemFrame.contains(location) {
|
||||||
if let item = item as? InstantPageTextItem, item.selectable {
|
if let item = item as? InstantPageTextItem, item.selectable {
|
||||||
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
|
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
|
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet?.dismissAnimated()
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
if canOpenIn {
|
||||||
|
strongSelf.openUrlIn(url)
|
||||||
|
} else {
|
||||||
strongSelf.openUrl(url)
|
strongSelf.openUrl(url)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
ActionSheetButtonItem(title: self.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: self.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
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 {
|
for item in items {
|
||||||
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
|
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
|
||||||
return (item, 0.0)
|
return (item, 0.0, [])
|
||||||
} else if let item = item as? InstantPageTextItem {
|
} else if let item = item as? InstantPageTextItem {
|
||||||
if let lineIndex = item.anchors[anchor] {
|
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 {
|
else if let item = item as? InstantPageDetailsItem {
|
||||||
if let anchorItem = findAnchorItem(anchor, items: item.items) {
|
if let (foundItem, offset, detailsItems) = self.findAnchorItem(anchor, items: item.items) {
|
||||||
return anchorItem
|
var detailsItems = detailsItems
|
||||||
|
detailsItems.insert(item, at: 0)
|
||||||
|
return (foundItem, offset, detailsItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentReferenceView(item: InstantPageTextItem) {
|
||||||
|
let controller = InstantPageReferenceController(account: self.account, item: item)
|
||||||
|
self.present(controller, nil)
|
||||||
|
}
|
||||||
|
|
||||||
private func openUrl(_ url: InstantPageUrlItem) {
|
private func openUrl(_ url: InstantPageUrlItem) {
|
||||||
guard let items = self.currentLayout?.items else {
|
guard let items = self.currentLayout?.items else {
|
||||||
return
|
return
|
||||||
@ -950,15 +958,43 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var baseUrl = url.url
|
var baseUrl = url.url
|
||||||
var anchor: String?
|
var anchor: String?
|
||||||
if let anchorRange = url.url.range(of: "#") {
|
if let anchorRange = url.url.range(of: "#") {
|
||||||
anchor = String(baseUrl[anchorRange.upperBound...])
|
anchor = String(baseUrl[anchorRange.upperBound...]).removingPercentEncoding
|
||||||
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
|
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
|
||||||
}
|
}
|
||||||
|
|
||||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl, let anchor = anchor {
|
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl, let anchor = anchor {
|
||||||
if !anchor.isEmpty {
|
if !anchor.isEmpty {
|
||||||
if let (item, offset) = findAnchorItem(String(anchor), items: items) {
|
if let (item, lineOffset, detailsItems) = findAnchorItem(String(anchor), items: items) {
|
||||||
let frame = effectiveFrameForItem(item)
|
var previousDetailsItem: InstantPageDetailsItem?
|
||||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: frame.minY + offset - self.scrollNode.view.contentInset.top), animated: true)
|
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 {
|
} else {
|
||||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top), animated: true)
|
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] {
|
private func mediasFromItems(_ items: [InstantPageItem]) -> [InstantPageMedia] {
|
||||||
var medias: [InstantPageMedia] = []
|
var medias: [InstantPageMedia] = []
|
||||||
for item in items {
|
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 {
|
if var currentExpandedDetails = self.currentExpandedDetails {
|
||||||
currentExpandedDetails[index] = expanded
|
currentExpandedDetails[index] = expanded
|
||||||
self.currentExpandedDetails = currentExpandedDetails
|
self.currentExpandedDetails = currentExpandedDetails
|
||||||
}
|
}
|
||||||
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: true)
|
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSettings() {
|
private func presentSettings() {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
var currentExpandedDetails: [Int : Bool]?
|
var currentExpandedDetails: [Int : Bool]?
|
||||||
var currentDetailsItems: [InstantPageDetailsItem] = []
|
var currentDetailsItems: [InstantPageDetailsItem] = []
|
||||||
|
|
||||||
var requestLayoutUpdate: (() -> Void)?
|
var requestLayoutUpdate: ((Bool) -> Void)?
|
||||||
|
|
||||||
var currentLayout: InstantPageLayout
|
var currentLayout: InstantPageLayout
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
@ -201,8 +201,8 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
itemNode = newNode
|
itemNode = newNode
|
||||||
|
|
||||||
if let itemNode = itemNode as? InstantPageDetailsNode {
|
if let itemNode = itemNode as? InstantPageDetailsNode {
|
||||||
itemNode.requestLayoutUpdate = { [weak self] in
|
itemNode.requestLayoutUpdate = { [weak self] animated in
|
||||||
self?.requestLayoutUpdate?()
|
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 {
|
if var currentExpandedDetails = self.currentExpandedDetails {
|
||||||
currentExpandedDetails[index] = expanded
|
currentExpandedDetails[index] = expanded
|
||||||
self.currentExpandedDetails = currentExpandedDetails
|
self.currentExpandedDetails = currentExpandedDetails
|
||||||
}
|
}
|
||||||
self.requestLayoutUpdate?()
|
self.requestLayoutUpdate?(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
||||||
@ -331,7 +331,7 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
return contentOffset
|
return contentOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
|
func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
|
||||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||||
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
|
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
|
||||||
return detailsNode
|
return detailsNode
|
||||||
@ -361,7 +361,7 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
return CGRect(origin: origin, size: tile.frame.size)
|
return CGRect(origin: origin, size: tile.frame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
|
func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
|
||||||
let layoutOrigin = item.frame.origin
|
let layoutOrigin = item.frame.origin
|
||||||
var origin = layoutOrigin
|
var origin = layoutOrigin
|
||||||
|
|
||||||
@ -454,7 +454,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
var previousNode: InstantPageDetailsNode?
|
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) {
|
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
|
self.account = account
|
||||||
@ -522,8 +522,8 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.contentNode.requestLayoutUpdate = { [weak self] in
|
self.contentNode.requestLayoutUpdate = { [weak self] animated in
|
||||||
self?.requestLayoutUpdate?()
|
self?.requestLayoutUpdate?(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update(strings: strings, theme: theme)
|
self.update(strings: strings, theme: theme)
|
||||||
@ -531,12 +531,12 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
@objc func buttonPressed() {
|
@objc func buttonPressed() {
|
||||||
self.setExpanded(!self.expanded, animated: true)
|
self.setExpanded(!self.expanded, animated: true)
|
||||||
|
self.updateExpanded(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setExpanded(_ expanded: Bool, animated: Bool) {
|
func setExpanded(_ expanded: Bool, animated: Bool) {
|
||||||
self.expanded = expanded
|
self.expanded = expanded
|
||||||
self.arrowNode.setOpen(expanded, animated: animated)
|
self.arrowNode.setOpen(expanded, animated: animated)
|
||||||
self.updateExpanded(expanded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ final private class InstantPageProgressNode: ASDisplayNode {
|
|||||||
|
|
||||||
let transition: ContainedViewLayoutTransition
|
let transition: ContainedViewLayoutTransition
|
||||||
if animated {
|
if animated {
|
||||||
transition = .animated(duration: 0.5, curve: .spring)
|
transition = .animated(duration: 0.7, curve: .spring)
|
||||||
} else {
|
} else {
|
||||||
transition = .immediate
|
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)? {
|
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 {
|
func matchesAnchor(_ anchor: String) -> Bool {
|
||||||
|
|||||||
@ -19,13 +19,13 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
private var localIsVisible = false
|
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.account = account
|
||||||
self.media = media
|
self.media = media
|
||||||
self.interactive = interactive
|
self.interactive = interactive
|
||||||
self.openMedia = openMedia
|
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()
|
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 minCellWidth: CGFloat = 1.0
|
||||||
var maxCellWidth: CGFloat = 1.0
|
var maxCellWidth: CGFloat = 1.0
|
||||||
if let text = cell.text {
|
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
|
minCellWidth = shortestTextItem.effectiveWidth() + totalCellPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
if let longestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage).0 {
|
if let longestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidthLimit - totalCellPadding), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage).0 {
|
||||||
maxCellWidth = longestTextItem.effectiveWidth() + totalCellPadding
|
maxCellWidth = max(minCellWidth, longestTextItem.effectiveWidth() + totalCellPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cell.colspan > 1 {
|
if cell.colspan > 1 {
|
||||||
@ -369,7 +369,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
|
|||||||
let delta = minSpanWidth - minWidth
|
let delta = minSpanWidth - minWidth
|
||||||
for i in range {
|
for i in range {
|
||||||
if let columnWidth = minColumnWidths[i] {
|
if let columnWidth = minColumnWidths[i] {
|
||||||
let growth = round(delta / CGFloat(range.count))
|
let growth = floor(delta / CGFloat(range.count))
|
||||||
minColumnWidths[i] = columnWidth + growth
|
minColumnWidths[i] = columnWidth + growth
|
||||||
availableWidth -= growth
|
availableWidth -= growth
|
||||||
}
|
}
|
||||||
@ -441,7 +441,6 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
|
|||||||
origin.x += width
|
origin.x += width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
k += cell.colspan
|
k += cell.colspan
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@ -462,7 +461,7 @@ func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: Instant
|
|||||||
var additionalItems: [InstantPageItem] = []
|
var additionalItems: [InstantPageItem] = []
|
||||||
var cellHeight: CGFloat?
|
var cellHeight: CGFloat?
|
||||||
if let text = cell.text {
|
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 {
|
if let textItem = textItem {
|
||||||
isEmptyRow = false
|
isEmptyRow = false
|
||||||
textItem.frame = textItem.frame.offsetBy(dx: tableCellInsets.left, dy: 0.0)
|
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 {
|
for i in 0 ..< self.lines.count {
|
||||||
let line = self.lines[i]
|
let line = self.lines[i]
|
||||||
|
|
||||||
let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment)
|
let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment)
|
||||||
if lineFrame.maxY < upperOriginBound || lineFrame.minY > lowerOriginBound {
|
if lineFrame.maxY < upperOriginBound || lineFrame.minY > lowerOriginBound {
|
||||||
continue
|
continue
|
||||||
@ -131,10 +130,11 @@ final class InstantPageTextItem: InstantPageItem {
|
|||||||
if !line.markedItems.isEmpty {
|
if !line.markedItems.isEmpty {
|
||||||
context.saveGState()
|
context.saveGState()
|
||||||
for item in line.markedItems {
|
for item in line.markedItems {
|
||||||
|
let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0)
|
||||||
context.setFillColor(item.color.cgColor)
|
context.setFillColor(item.color.cgColor)
|
||||||
|
|
||||||
let height = floor(item.frame.size.height * 2.2)
|
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)
|
let path = UIBezierPath.init(roundedRect: rect, cornerRadius: 3.0)
|
||||||
context.addPath(path.cgPath)
|
context.addPath(path.cgPath)
|
||||||
context.fillPath()
|
context.fillPath()
|
||||||
@ -146,7 +146,8 @@ final class InstantPageTextItem: InstantPageItem {
|
|||||||
|
|
||||||
if !line.strikethroughItems.isEmpty {
|
if !line.strikethroughItems.isEmpty {
|
||||||
for item in line.strikethroughItems {
|
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 {
|
switch text {
|
||||||
case .empty:
|
case .empty:
|
||||||
return NSAttributedString(string: "", attributes: styleStack.textAttributes())
|
return NSAttributedString(string: "", attributes: styleStack.textAttributes())
|
||||||
@ -451,7 +452,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
|
|||||||
case let .concat(texts):
|
case let .concat(texts):
|
||||||
let string = NSMutableAttributedString()
|
let string = NSMutableAttributedString()
|
||||||
for text in texts {
|
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)
|
string.append(substring)
|
||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
@ -483,6 +484,11 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
|
|||||||
let descent: CGFloat
|
let descent: CGFloat
|
||||||
let width: CGFloat
|
let width: CGFloat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dimensions = dimensions
|
||||||
|
if let boundingWidth = boundingWidth {
|
||||||
|
dimensions = dimensions.fittedToWidthOrSmaller(boundingWidth)
|
||||||
|
}
|
||||||
let extentBuffer = UnsafeMutablePointer<RunStruct>.allocate(capacity: 1)
|
let extentBuffer = UnsafeMutablePointer<RunStruct>.allocate(capacity: 1)
|
||||||
extentBuffer.initialize(to: RunStruct(ascent: 0.0, descent: 0.0, width: dimensions.width))
|
extentBuffer.initialize(to: RunStruct(ascent: 0.0, descent: 0.0, width: dimensions.width))
|
||||||
var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in
|
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 line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0)
|
||||||
var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
|
var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
|
||||||
let lineRange = NSMakeRange(lastIndex, lineCharacterCount)
|
let lineRange = NSMakeRange(lastIndex, lineCharacterCount)
|
||||||
|
let substring = string.attributedSubstring(from: lineRange).string
|
||||||
|
|
||||||
var stop = false
|
var stop = false
|
||||||
if maxNumberOfLines > 0 && lines.count == maxNumberOfLines - 1 && lastIndex + lineCharacterCount < string.length {
|
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)
|
indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2559,19 +2559,40 @@ func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumA
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteArtworkData: Signal<(Data?, Data?, Bool), NoError> = .single((nil, nil, false))
|
var immediateArtworkData: Signal<(Data?, Data?, Bool), NoError> = .single((nil, nil, false))
|
||||||
if let albumArt = albumArt {
|
|
||||||
|
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 {
|
if thumbnail {
|
||||||
remoteArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource)
|
immediateArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource)
|
||||||
|> map { thumbnailData in
|
|> map { thumbnailData in
|
||||||
return (thumbnailData, nil, false)
|
return (thumbnailData, nil, false)
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
|> map { fileArtworkData, remoteArtworkData in
|
||||||
let remoteThumbnailData = remoteArtworkData.0
|
let remoteThumbnailData = remoteArtworkData.0
|
||||||
let remoteFullSizeData = remoteArtworkData.1
|
let remoteFullSizeData = remoteArtworkData.1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user