Merge branch 'master' of gitlab.com:peter-iakovlev/TelegramUI

# Conflicts:
#	TelegramUI/PresentationStrings.swift
#	TelegramUI/Resources/PresentationStrings.mapping
This commit is contained in:
overtake 2018-11-20 19:15:53 +04:00
commit 7a4c6f8bfc
34 changed files with 2029 additions and 1710 deletions

View File

@ -35,6 +35,7 @@
0958FBB9218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */; };
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 */; };
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 */; };
@ -1094,6 +1095,7 @@
0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageFeedbackItem.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>"; };
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageScrollableNode.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>"; };
@ -3117,6 +3119,7 @@
0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */,
0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */,
0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */,
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */,
);
name = "Instant Page";
sourceTree = "<group>";
@ -5108,6 +5111,7 @@
D0EC6D231EB9F58800EBF1C3 /* StickerResources.swift in Sources */,
09C9EA3821A044B500E90146 /* StringForDuration.swift in Sources */,
D0EC6D241EB9F58800EBF1C3 /* CachedResourceRepresentations.swift in Sources */,
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */,
D01BAA201ECC9A2500295217 /* CallListNodeLocation.swift in Sources */,
D0EC6D251EB9F58800EBF1C3 /* FetchCachedRepresentations.swift in Sources */,
D0EC6D261EB9F58800EBF1C3 /* TransformOutgoingMessageMedia.swift in Sources */,

View File

@ -80,6 +80,10 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
self?.requestNextOption?()
}
self.controllerNode.updateNextEnabled = { [weak self] value in
self?.navigationItem.rightBarButtonItem?.isEnabled = value
}
if let (number, codeType, nextType, timeout) = self.data {
self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout)
}

View File

@ -84,6 +84,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var loginWithCode: ((String) -> Void)?
var requestNextOption: (() -> Void)?
var requestAnotherOption: (() -> Void)?
var updateNextEnabled: ((Bool) -> Void)?
var inProgress: Bool = false {
didSet {
@ -188,6 +189,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
func updateCode(_ code: String) {
self.codeField.textField.text = code
self.codeFieldTextChanged(self.codeField.textField)
if let codeType = self.codeType {
var codeLength: Int32?
switch codeType {
@ -303,6 +305,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
}
@objc func codeFieldTextChanged(_ textField: UITextField) {
self.updateNextEnabled?(!(textField.text ?? "").isEmpty)
if let codeType = self.codeType {
var codeLength: Int32?
switch codeType {

View File

@ -277,11 +277,11 @@ public final class AuthorizationSequenceController: NavigationController {
case .generic:
text = strongSelf.strings.Login_UnknownError
case .codeExpired:
text = strongSelf.strings.Login_CodeExpired
let account = strongSelf.account
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty))
}).start()
return
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: strongSelf.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_OK, action: {})]), in: .window(.root))

View File

@ -44,6 +44,17 @@ final class AuthorizationSequenceSignUpController: ViewController {
self.statusBar.statusBarStyle = self.theme.statusBarStyle
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
self.attemptNavigation = { [weak self] f in
guard let strongSelf = self else {
return true
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: theme), title: nil, text: strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: strings.Login_CancelPhoneVerificationContinue, action: {
}), TextAlertAction(type: .defaultAction, title: strings.Login_CancelPhoneVerificationStop, action: {
f()
})]), in: .window(.root))
return false
}
}
required init(coder aDecoder: NSCoder) {

View File

@ -96,7 +96,7 @@ final class AuthorizationSequenceSplashController: ViewController {
}
private func addControllerIfNeeded() {
if !controller.isViewLoaded {
if !controller.isViewLoaded || controller.view.superview == nil {
self.displayNode.view.addSubview(controller.view)
controller.view.frame = self.displayNode.bounds;
controller.viewDidAppear(false)

View File

@ -4915,7 +4915,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
if let applicationContext = self.account.applicationContext as? TelegramApplicationContext {
let actionSheet = OpenInActionSheetController(postbox: self.account.postbox, applicationContext: applicationContext, theme: self.presentationData.theme, strings: self.presentationData.strings, item: .url(url: url), openUrl: { [weak self] url in
if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext, let navigationController = strongSelf.navigationController as? NavigationController {
openExternalUrl(account: strongSelf.account, url: url, presentationData: strongSelf.presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: {
openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: strongSelf.presentationData, applicationContext: applicationContext, navigationController: navigationController, dismissInput: {
self?.chatDisplayNode.dismissInput()
})
}

View File

@ -705,7 +705,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
transition.updateBounds(node: self.historyNode, bounds: CGRect(origin: CGPoint(), size: contentBounds.size))
transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0))
self.loadingNode.updateLayout(size: contentBounds.size, insets: insets, transition: transition)
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
if let restrictedNode = self.restrictedNode {
@ -855,6 +854,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
self.loadingNode.updateLayout(size: contentBounds.size, insets: UIEdgeInsetsMake(containerInsets.top, 0.0, containerInsets.bottom + contentBottomInset, 0.0), transition: transition)
if let containerNode = self.containerNode {
contentBottomInset += 8.0
let containerNodeFrame = CGRect(origin: CGPoint(x: wrappingInsets.left, y: wrappingInsets.top), size: CGSize(width: contentBounds.size.width, height: contentBounds.size.height - containerInsets.bottom - inputPanelsHeight - 8.0))

View File

@ -344,11 +344,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
} else {
self.dateNode.attributedText = nil
}
//self.deleteButton.isHidden = !canDelete
self.requestLayout?(.immediate)
}
self.deleteButton.isHidden = origin == nil
}
func setMessage(_ message: Message) {
@ -695,7 +695,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
if let strongSelf = self {
let openInController = OpenInActionSheetController(postbox: strongSelf.account.postbox, applicationContext: strongSelf.account.telegramApplicationContext, theme: presentationData.theme, strings: presentationData.strings, item: item, additionalAction: nil, openUrl: { [weak self] url in
if let strongSelf = self, let applicationContext = strongSelf.account.applicationContext as? TelegramApplicationContext {
openExternalUrl(account: strongSelf.account, url: url, presentationData: presentationData, applicationContext: applicationContext, navigationController: nil, dismissInput: {})
openExternalUrl(account: strongSelf.account, url: url, forceExternal: true, presentationData: presentationData, applicationContext: applicationContext, navigationController: nil, dismissInput: {})
}
})
strongSelf.controllerInteraction?.presentController(openInController, nil)

View File

@ -195,7 +195,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
guard let strongSelf = self else {
return
}
strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, type: .shieldSuccess(strongSelf.presentationData.strings.Passcode_AppLockedAlert)), in: .window(.root))
strongSelf.presentInGlobalOverlay(OverlayStatusController(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, type: .shieldSuccess(strongSelf.presentationData.strings.Passcode_AppLockedAlert)))
})
}
}

View File

@ -513,20 +513,25 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
if secretBeginTimeAndTimeout?.0 != nil {
progressRequired = true
} else if let fetchStatus = self.fetchStatus {
if case .Local = fetchStatus {
if let file = media as? TelegramMediaFile, file.isVideo {
progressRequired = true
} else if isSecretMedia {
progressRequired = true
} else if let webpage = webpage, case let .Loaded(content) = webpage.content {
if content.embedUrl != nil {
switch fetchStatus {
case .Local:
if let file = media as? TelegramMediaFile, file.isVideo {
progressRequired = true
} else if let file = content.file, file.isVideo, !file.isAnimated {
} else if isSecretMedia {
progressRequired = true
} else if let webpage = webpage, case let .Loaded(content) = webpage.content {
if content.embedUrl != nil {
progressRequired = true
} else if let file = content.file, file.isVideo, !file.isAnimated {
progressRequired = true
}
}
case .Remote, .Fetching:
if let _ = webpage, let automaticDownload = self.automaticDownload, automaticDownload {
progressRequired = false
} else {
progressRequired = true
}
}
} else {
progressRequired = true
}
}

View File

@ -138,10 +138,12 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let contrainedTextSize = CGSize(width: maximumTextWidth, height: constrainedSize.height)
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: textString, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
let size = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + leftInset, height: titleLayout.size.height + textLayout.size.height + 2 * spacing)
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: textString, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
let size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right) + leftInset, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing)
return (size, {
let node: ChatMessageReplyInfoNode
@ -185,8 +187,8 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
node.imageNode = nil
}
titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: spacing), size: titleLayout.size)
textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleNode.frame.maxY + spacing), size: textLayout.size)
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size)
textNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top), size: textLayout.size)
node.lineNode.image = lineImage
node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 5.0)))

View File

@ -171,25 +171,27 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
cutout = TextNodeCutout(bottomRight: statusSize)
}
let textInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0)
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets))
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))
var statusFrame: CGRect?
if let statusSize = statusSize {
statusFrame = CGRect(origin: CGPoint(x: textFrame.maxX - textInsets.right - statusSize.width, y: textFrame.maxY - textInsets.bottom - statusSize.height), size: statusSize)
statusFrame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.maxX - statusSize.width, y: textFrameWithoutInsets.maxY - statusSize.height), size: statusSize)
}
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
statusFrame = statusFrame?.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var boundingSize: CGSize
if let statusFrame = statusFrame {
boundingSize = textFrame.union(statusFrame).size
boundingSize = textFrameWithoutInsets.union(statusFrame).size
} else {
boundingSize = textFrame.size
boundingSize = textFrameWithoutInsets.size
}
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom

View File

@ -1,5 +1,7 @@
import UIKit
import Accelerate
import Display
import TelegramCore
private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? {
var sourceBuffer = vImage_Buffer()
@ -58,22 +60,57 @@ func imageHasTransparency(_ cgImage: CGImage) -> Bool {
return false
}
func imageIsMonochrome(_ cgImage: CGImage) -> Bool {
private func scaledContext(_ 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)
}
let context = DrawingContext(size: size, scale: 1.0, clear: true)
context.withFlippedContext { context in
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
}
return context
}
func imageRequiresInversion(_ cgImage: CGImage) -> Bool {
guard cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else {
return false
}
if let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) {
guard [.first, .last, .premultipliedFirst, .premultipliedLast].contains(cgImage.alphaInfo) else {
return false
}
// SSE, bias = 0, [0,0,0]
// if adjust_color_bias:
// bias = ImageStat.Stat(thumb).mean[:3]
// bias = [b - sum(bias)/3 for b in bias ]
// for pixel in thumb.getdata():
// mu = sum(pixel)/3
// SSE += sum((pixel[i] - mu - bias[i])*(pixel[i] - mu - bias[i]) for i in [0,1,2])
let context = scaledContext(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
}
}
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
}
}
}
}
return CGFloat(matching) / CGFloat(total) > 0.85
}
return false
}

View File

@ -54,6 +54,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let resolveUrlDisposable = MetaDisposable()
private let loadWebpageDisposable = MetaDisposable()
private let loadProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true)
private let loadProgressDisposable = MetaDisposable()
private let updateLayoutDisposable = MetaDisposable()
private var themeReferenceDate: Date?
@ -118,12 +121,18 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -strongSelf.scrollNode.view.contentInset.top), animated: true)
}
}
self.loadProgressDisposable.set((self.loadProgress.get()
|> deliverOnMainQueue).start(next: { [weak self] value in
self?.navigationBar.setLoadProgress(value)
}))
}
deinit {
self.hiddenMediaDisposable.dispose()
self.resolveUrlDisposable.dispose()
self.loadWebpageDisposable.dispose()
self.loadProgressDisposable.dispose()
}
func update(settings: InstantPagePresentationSettings, strings: PresentationStrings) {
@ -404,6 +413,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var embedIndex = -1
var detailsIndex = -1
var previousDetailsNode: InstantPageDetailsNode?
for item in self.currentLayoutItemsWithNodes {
itemIndex += 1
if item is InstantPageWebEmbedItem {
@ -462,9 +473,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}, currentExpandedDetails: self.currentExpandedDetails) {
newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition)
// if case let .animated(duration, _) = transition {
// newNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
// }
if let topNode = topNode {
self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode)
} else {
@ -480,6 +488,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: true)
}
}
if let previousDetailsNode = previousDetailsNode {
if itemNode.frame.minY - previousDetailsNode.frame.maxY < 1.0 {
itemNode.previousNode = previousDetailsNode
}
}
previousDetailsNode = itemNode
}
}
} else {
@ -676,8 +691,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
let itemFrame = effectiveFrameForItem(item)
if itemFrame.contains(location) {
var contentOffset = CGPoint()
if let item = item as? InstantPageTableItem {
contentOffset = tableContentOffset(item: item)
if let item = item as? InstantPageScrollableItem {
contentOffset = scrollableContentOffset(item: item)
}
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY))
@ -713,10 +728,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
private func tableContentOffset(item: InstantPageTableItem) -> CGPoint {
private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageTableNode, itemNode.item === item {
if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item {
contentOffset = itemNode.contentOffset
break
}
@ -778,12 +793,12 @@ 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)
let itemFrame = self.effectiveFrameForItem(item).insetBy(dx: -2.0, dy: -2.0)
if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageTableItem {
let contentOffset = tableContentOffset(item: item)
} else if let item = item as? InstantPageScrollableItem {
let contentOffset = scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
}
@ -951,35 +966,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return
}
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
guard let strongSelf = self else {
return EmptyDisposable
}
let controller = OverlayStatusController(theme: strongSelf.presentationTheme, strings: strongSelf.strings, type: .loading(cancelled: {
cancelImpl?()
}))
strongSelf.present(controller, nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
self.loadProgress.set(0.02)
let resolveSignal = resolveUrl(account: self.account, url: url.url)
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = { [weak self] in
self?.resolveUrlDisposable.set(nil)
|> afterCompleted { [weak self] in
self?.loadProgress.set(0.07)
}
self.resolveUrlDisposable.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
switch result {
@ -989,9 +981,19 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let anchorRange = externalUrl.range(of: "#") {
anchor = String(externalUrl[anchorRange.upperBound...])
}
strongSelf.loadWebpageDisposable.set((webpagePreview(account: strongSelf.account, url: externalUrl, webpageId: webpageId) |> deliverOnMainQueue).start(next: { webpage in
if let strongSelf = self, let webpage = webpage {
strongSelf.pushController(InstantPageController(account: strongSelf.account, webPage: webpage, anchor: anchor))
strongSelf.loadWebpageDisposable.set((webpagePreviewWithProgress(account: strongSelf.account, url: externalUrl, webpageId: webpageId)
|> deliverOnMainQueue).start(next: { result in
if let strongSelf = self {
switch result {
case let .result(webpage):
if let webpage = webpage {
strongSelf.loadProgress.set(1.0)
strongSelf.pushController(InstantPageController(account: strongSelf.account, webPage: webpage, anchor: anchor))
}
break
case let .progress(progress):
strongSelf.loadProgress.set(CGFloat(0.07 + progress * (1.0 - 0.07)))
}
}
}))
} else {
@ -1072,14 +1074,18 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return
}
var medias: [InstantPageMedia] = mediasFromItems(items)
medias = medias.filter {
$0.media is TelegramMediaImage
}
var entries: [InstantPageGalleryEntry] = []
for media in medias {
entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count))))
if media.media is TelegramMediaWebpage {
entries.append(InstantPageGalleryEntry(index: 0, pageId: webPage.webpageId, media: media, caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: 0, totalCount: 1)))
} else {
var medias: [InstantPageMedia] = mediasFromItems(items)
medias = medias.filter {
$0.media is TelegramMediaImage
}
for media in medias {
entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count))))
}
}
var centralIndex: Int?

View File

@ -320,10 +320,10 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
}
}
private func tableContentOffset(item: InstantPageTableItem) -> CGPoint {
private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageTableNode, itemNode.item === item {
if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item {
contentOffset = itemNode.contentOffset
break
}
@ -388,8 +388,8 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageTableItem {
let contentOffset = tableContentOffset(item: item)
} else if let item = item as? InstantPageScrollableItem {
let contentOffset = scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
}
@ -446,12 +446,14 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
private let highlightedBackgroundNode: ASDisplayNode
private let buttonNode: HighlightableButtonNode
private let arrowNode: InstantPageDetailsArrowNode
private let separatorNode: ASDisplayNode
let separatorNode: ASDisplayNode
let contentNode: InstantPageDetailsContentNode
private let updateExpanded: (Bool) -> Void
var expanded: Bool
var previousNode: InstantPageDetailsNode?
var requestLayoutUpdate: (() -> 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) {
@ -503,15 +505,18 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
if highlighted {
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.highlightedBackgroundNode.alpha = 1.0
if strongSelf.separatorNode.frame.minY < strongSelf.highlightedBackgroundNode.frame.maxY {
strongSelf.separatorNode.alpha = 0.0
strongSelf.separatorNode.alpha = 0.0
if let previousSeparator = strongSelf.previousNode?.separatorNode {
previousSeparator.alpha = 0.0
}
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
if strongSelf.separatorNode.alpha < 1.0 {
strongSelf.separatorNode.alpha = 1.0
strongSelf.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
strongSelf.separatorNode.alpha = 1.0
strongSelf.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if let previousSeparator = strongSelf.previousNode?.separatorNode {
previousSeparator.alpha = 1.0
previousSeparator.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
}

View File

@ -64,7 +64,6 @@ struct InstantPageGalleryEntry: Equatable {
styleStack.push(.linkColor(UIColor(rgb: 0x5ac8fa)))
styleStack.push(.linkMarkerColor(UIColor(rgb: 0x5ac8fa, alpha: 0.2)))
styleStack.push(.fontSerif(false))
//styleStack.push(.lineSpacingFactor(1.0))
credit = attributedStringForRichText(mediaCredit, styleStack: styleStack)
} else {
credit = NSAttributedString(string: "")
@ -75,6 +74,12 @@ struct InstantPageGalleryEntry: Equatable {
return InstantImageGalleryItem(account: account, presentationData: presentationData, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions)
} else if let file = self.media.media as? TelegramMediaFile, file.isVideo {
return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .instantPage(self.pageId, file.fileId), fileReference: .webPage(webPage: WebpageReference(webPage), media: file)), originData: nil, indexData: GalleryItemIndexData(position: self.location.position, totalCount: self.location.totalCount), contentInfo: .webPage(webPage, file), caption: caption, credit: credit, openUrl: { _ in }, openUrlOptions: { _ in })
} else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content {
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent) {
return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: nil, caption: NSAttributedString(string: ""), openUrl: { _ in }, openUrlOptions: { _ in })
} else {
preconditionFailure()
}
} else {
preconditionFailure()
}

View File

@ -17,6 +17,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
private let openMedia: (InstantPageMedia) -> Void
private let imageNode: TransformImageNode
private let statusNode: RadialStatusNode
private let linkIconNode: ASImageNode
private let pinNode: ChatMessageLiveLocationPositionNode
@ -38,6 +39,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.openMedia = openMedia
self.imageNode = TransformImageNode()
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
self.linkIconNode = ASImageNode()
self.pinNode = ChatMessageLiveLocationPositionNode()
@ -80,6 +82,8 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference))
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start())
self.statusNode.transitionToState(.play(.white), animated: false, completion: {})
self.addSubnode(statusNode)
}
}
@ -92,6 +96,8 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
if self.interactive {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
} else {
self.view.isUserInteractionEnabled = false
}
}
@ -102,7 +108,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
if self.theme.imageEmptyColor != theme.imageEmptyColor {
if self.theme.imageTintColor != theme.imageTintColor {
self.theme = theme
self.themeUpdated = true
self.setNeedsLayout()
@ -130,7 +136,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.linkIconNode.frame = CGRect(x: size.width - 38.0, y: 14.0, width: 24.0, height: 24.0)
} else if let file = self.media.media as? TelegramMediaFile, let dimensions = file.dimensions {
let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageEmptyColor : nil
let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageTintColor : nil
let imageSize = dimensions.aspectFilled(size)
let boundingSize = size
@ -155,6 +161,16 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let (pinSize, pinApply) = makePinLayout(self.account, theme, nil, false)
self.pinNode.frame = CGRect(origin: CGPoint(x: floor((size.width - pinSize.width) / 2.0), y: floor(size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize)
pinApply()
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) {
let imageSize = largest.dimensions.aspectFilled(size)
let boundingSize = size
let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0
let makeLayout = self.imageNode.asyncLayout()
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.theme.pageBackgroundColor))
apply()
let radialStatusSize: CGFloat = 50.0
self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize)
}
}
}
@ -172,6 +188,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
func updateHiddenMedia(media: InstantPageMedia?) {
self.imageNode.isHidden = self.media == media
self.statusNode.isHidden = self.imageNode.isHidden
}
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {

View File

@ -164,7 +164,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
case let .paragraph(text):
let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage)
let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: horizontalInset, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage)
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
case let .preformatted(text):
let styleStack = InstantPageTextStyleStack()
@ -215,12 +215,12 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
} else {
value = "\(i + 1)."
}
let (textItem, textItems, _) = layoutTextItemWithString(attributedStringForRichText(.plain(value), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint())
let (textItem, _, _) = layoutTextItemWithString(attributedStringForRichText(.plain(value), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint())
if let textItem = textItem, let line = textItem.lines.first {
textItem.selectable = false
maxIndexWidth = max(maxIndexWidth, line.frame.width)
indexItems.append(textItem)
}
indexItems.append(textItems.first!)
} else {
let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 6.0, height: 12.0)), shapeFrame: CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: CGSize(width: 6.0, height: 6.0)), shape: .ellipse, color: theme.textCategories.paragraph.color)
indexItems.append(shapeItem)
@ -614,11 +614,11 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
var contentSize: CGSize
let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size)
let item: InstantPageItem
if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage {
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: size, duration: nil, author: nil, image: image, file: nil, instantPage: nil)
let content = TelegramMediaWebpageContent.Loaded(loadedContent)
item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false)
item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false)
} else {
item = InstantPageWebEmbedItem(frame: frame, url: url, html: html, enableScrolling: allowScrolling)

View File

@ -6,6 +6,49 @@ private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .w
private let moreImage = generateTintedImage(image: UIImage(bundleImageName: "Instant View/MoreIcon"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Instant View/ActionIcon"), color: .white)
final private class InstantPageProgressNode: ASDisplayNode {
private let foregroundNode: ASDisplayNode
private var progress: CGFloat = 0.0
override init() {
self.foregroundNode = ASDisplayNode()
self.foregroundNode.backgroundColor = .white
super.init()
self.addSubnode(self.foregroundNode)
}
func setProgress(_ progress: CGFloat, animated: Bool = false) {
if self.progress == progress && animated {
return
}
let size = self.bounds.size
self.progress = progress
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.5, curve: .spring)
} else {
transition = .immediate
}
let alpaTransition: ContainedViewLayoutTransition
if animated {
alpaTransition = .animated(duration: 0.3, curve: .easeInOut)
} else {
alpaTransition = .immediate
}
transition.updateFrame(node: self.foregroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width * progress, height: size.height))
let alpha: CGFloat = progress < 0.001 || progress > 0.999 ? 0.0 : 1.0
alpaTransition.updateAlpha(node: self.foregroundNode, alpha: alpha)
}
}
final class InstantPageNavigationBar: ASDisplayNode {
private var strings: PresentationStrings
@ -17,7 +60,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
private let arrowNode: ASImageNode
private let titleNode: ASTextNode
private let progressNode: ASDisplayNode
private let progressNode: InstantPageProgressNode
private let intrinsicMoreSize: CGSize
private let intrinsicSmallMoreSize: CGSize
@ -65,8 +108,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 1
self.progressNode = ASDisplayNode()
self.progressNode.backgroundColor = .white
self.progressNode = InstantPageProgressNode()
super.init()
@ -116,6 +158,10 @@ final class InstantPageNavigationBar: ASDisplayNode {
}
}
func setLoadProgress(_ progress: CGFloat) {
self.progressNode.setProgress(progress, animated: true)
}
func updateLayout(size: CGSize, minHeight: CGFloat, maxHeight: CGFloat, topInset: CGFloat, leftInset: CGFloat, rightInset: CGFloat, title: String?, pageProgress: CGFloat, transition: ContainedViewLayoutTransition) {
let progressHeight: CGFloat
if !topInset.isZero {
@ -183,6 +229,9 @@ final class InstantPageNavigationBar: ASDisplayNode {
transition.updateAlpha(node: self.actionButton, alpha: alphaFactor)
transition.updateFrame(node: self.scrollToTopButton, frame: CGRect(origin: CGPoint(x: leftInset + 64.0, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset - 64.0, height: size.height)))
let loadProgressHeight: CGFloat = 2.0
transition.updateFrame(node: self.progressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - loadProgressHeight - UIScreenPixel), size: CGSize(width: size.width, height: loadProgressHeight)))
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

View File

@ -0,0 +1,106 @@
import Foundation
import TelegramCore
import Postbox
import Display
protocol InstantPageScrollableItem: class, InstantPageItem {
var contentSize: CGSize { get }
var horizontalInset: CGFloat { get }
var isRTL: Bool { get }
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)?
}
private final class InstantPageScrollableContentNodeParameters: NSObject {
let item: InstantPageScrollableItem
init(item: InstantPageScrollableItem) {
self.item = item
super.init()
}
}
final class InstantPageScrollableContentNode: ASDisplayNode {
let item: InstantPageScrollableItem
init(item: InstantPageScrollableItem, additionalNodes: [InstantPageNode]) {
self.item = item
super.init()
self.isOpaque = false
self.isUserInteractionEnabled = false
for case let node as ASDisplayNode in additionalNodes {
self.addSubnode(node)
}
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return InstantPageScrollableContentNodeParameters(item: self.item)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
let context = UIGraphicsGetCurrentContext()!
if let parameters = parameters as? InstantPageScrollableContentNodeParameters {
parameters.item.drawInTile(context: context)
}
}
}
final class InstantPageScrollableNode: ASScrollNode, InstantPageNode {
let item: InstantPageScrollableItem
let contentNode: InstantPageScrollableContentNode
var contentOffset: CGPoint {
return self.view.contentOffset
}
init(item: InstantPageScrollableItem, additionalNodes: [InstantPageNode]) {
self.item = item
self.contentNode = InstantPageScrollableContentNode(item: item, additionalNodes: additionalNodes)
super.init()
self.isOpaque = false
self.contentNode.frame = CGRect(origin: CGPoint(x: item.horizontalInset, y: 0.0), size: item.contentSize)
self.view.contentSize = CGSize(width: item.contentSize.width + item.horizontalInset * 2.0, height: item.contentSize.height)
if item.isRTL {
self.view.contentOffset = CGPoint(x: self.view.contentSize.width - item.frame.width, y: 0.0)
}
self.view.alwaysBounceVertical = false
self.view.showsHorizontalScrollIndicator = false
self.view.showsVerticalScrollIndicator = false
if #available(iOSApplicationExtension 11.0, *) {
self.view.contentInsetAdjustmentBehavior = .never
}
self.addSubnode(self.contentNode)
self.view.interactiveTransitionGestureRecognizerTest = { [weak self] point -> Bool in
if let strongSelf = self {
if strongSelf.view.contentOffset.x < 1.0 {
return false
} else {
return point.x - strongSelf.view.contentOffset.x > 30.0
}
} else {
return false
}
}
}
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
return nil
}
func updateHiddenMedia(media: InstantPageMedia?) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
}
}

View File

@ -94,7 +94,7 @@ private let tableCellInsets = UIEdgeInsetsMake(14.0, 12.0, 14.0, 12.0)
private let tableBorderWidth: CGFloat = 1.0
private let tableCornerRadius: CGFloat = 5.0
final class InstantPageTableItem: InstantPageItem {
final class InstantPageTableItem: InstantPageScrollableItem {
var frame: CGRect
let totalWidth: CGFloat
let horizontalInset: CGFloat
@ -104,7 +104,7 @@ final class InstantPageTableItem: InstantPageItem {
let theme: InstantPageTheme
let rtl: Bool
let isRTL: Bool
fileprivate let cells: [InstantPageTableCellItem]
private let borderWidth: CGFloat
@ -115,7 +115,11 @@ final class InstantPageTableItem: InstantPageItem {
self.borderWidth = borderWidth
self.theme = theme
self.cells = cells
self.rtl = rtl
self.isRTL = rtl
}
var contentSize: CGSize {
return CGSize(width: self.totalWidth, height: self.frame.height)
}
func drawInTile(context: CGContext) {
@ -175,11 +179,22 @@ final class InstantPageTableItem: 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 InstantPageTableNode(item: self, account: account, strings: strings, theme: theme)
var additionalNodes: [InstantPageNode] = []
for cell in self.cells {
for item in cell.additionalItems {
if item.wantsNode {
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
additionalNodes.append(node)
}
}
}
}
return InstantPageScrollableNode(item: self, additionalNodes: additionalNodes)
}
func matchesNode(_ node: InstantPageNode) -> Bool {
if let node = node as? InstantPageTableNode {
if let node = node as? InstantPageScrollableNode {
return node.item === self
}
return false
@ -213,107 +228,6 @@ final class InstantPageTableItem: InstantPageItem {
}
}
private final class InstantPageTableNodeParameters: NSObject {
let item: InstantPageTableItem
init(item: InstantPageTableItem) {
self.item = item
super.init()
}
}
final class InstantPageTableContentNode: ASDisplayNode {
private let item: InstantPageTableItem
init(item: InstantPageTableItem, account: Account, strings: PresentationStrings, theme: InstantPageTheme) {
self.item = item
super.init()
self.isOpaque = false
self.isUserInteractionEnabled = false
for cell in self.item.cells {
for item in cell.additionalItems {
if item.wantsNode {
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
self.addSubnode(node)
}
}
}
}
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return InstantPageTableNodeParameters(item: self.item)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
let context = UIGraphicsGetCurrentContext()!
if let parameters = parameters as? InstantPageTableNodeParameters {
parameters.item.drawInTile(context: context)
}
}
}
final class InstantPageTableNode: ASScrollNode, InstantPageNode {
let item: InstantPageTableItem
let contentNode: InstantPageTableContentNode
var contentOffset: CGPoint {
return self.view.contentOffset
}
init(item: InstantPageTableItem, account: Account, strings: PresentationStrings, theme: InstantPageTheme) {
self.item = item
self.contentNode = InstantPageTableContentNode(item: item, account: account, strings: strings, theme: theme)
super.init()
self.isOpaque = false
self.contentNode.frame = CGRect(x: item.horizontalInset, y: 0.0, width: item.totalWidth, height: item.frame.height)
self.view.contentSize = CGSize(width: item.totalWidth + item.horizontalInset * 2.0, height: item.frame.height)
if item.rtl {
self.view.contentOffset = CGPoint(x: self.view.contentSize.width - item.frame.width, y: 0.0)
}
self.view.alwaysBounceVertical = false
self.view.showsHorizontalScrollIndicator = false
self.view.showsVerticalScrollIndicator = false
if #available(iOSApplicationExtension 11.0, *) {
self.view.contentInsetAdjustmentBehavior = .never
}
self.addSubnode(self.contentNode)
self.view.interactiveTransitionGestureRecognizerTest = { [weak self] point -> Bool in
if let strongSelf = self {
if strongSelf.view.contentOffset.x < 1.0 {
return false
} else {
return point.x - strongSelf.view.contentOffset.x > 30.0
}
} else {
return false
}
}
}
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
return nil
}
func updateHiddenMedia(media: InstantPageMedia?) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
}
}
private struct TableRow {
var minColumnWidths: [Int : CGFloat]
var maxColumnWidths: [Int : CGFloat]

View File

@ -324,6 +324,83 @@ final class InstantPageTextItem: InstantPageItem {
}
}
final class InstantPageScrollableTextItem: InstantPageScrollableItem {
var frame: CGRect
let totalWidth: CGFloat
let horizontalInset: CGFloat
let medias: [InstantPageMedia] = []
let wantsNode: Bool = true
let separatesTiles: Bool = false
let item: InstantPageTextItem
let additionalItems: [InstantPageItem]
let isRTL: Bool
fileprivate init(frame: CGRect, item: InstantPageTextItem, additionalItems: [InstantPageItem], totalWidth: CGFloat, horizontalInset: CGFloat, rtl: Bool) {
self.frame = frame
self.item = item
self.additionalItems = additionalItems
self.totalWidth = totalWidth
self.horizontalInset = horizontalInset
self.isRTL = rtl
}
var contentSize: CGSize {
return CGSize(width: self.totalWidth, height: self.frame.height)
}
func drawInTile(context: CGContext) {
context.saveGState()
context.translateBy(x: self.item.frame.minX, y: self.item.frame.minY)
self.item.drawInTile(context: context)
context.restoreGState()
}
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]?) -> (ASDisplayNode & InstantPageNode)? {
var additionalNodes: [InstantPageNode] = []
for item in additionalItems {
if item.wantsNode {
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
node.frame = item.frame
additionalNodes.append(node)
}
}
}
return InstantPageScrollableNode(item: self, additionalNodes: additionalNodes)
}
func matchesAnchor(_ anchor: String) -> Bool {
return self.item.matchesAnchor(anchor)
}
func matchesNode(_ node: InstantPageNode) -> Bool {
if let node = node as? InstantPageScrollableNode {
return node.item === self
}
return false
}
func distanceThresholdGroup() -> Int? {
return nil
}
func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat {
return 0.0
}
func linkSelectionRects(at point: CGPoint) -> [CGRect] {
let rects = self.item.linkSelectionRects(at: point.offsetBy(dx: -self.item.frame.minX - self.horizontalInset, dy: -self.item.frame.minY))
return rects.map { $0.offsetBy(dx: self.item.frame.minX + self.horizontalInset, dy: -self.item.frame.minY) }
}
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if self.item.selectable, self.item.frame.contains(location.offsetBy(dx: -self.item.frame.minX - self.horizontalInset, dy: -self.item.frame.minY)) {
return (item, self.item.frame.origin.offsetBy(dx: self.horizontalInset, dy: -self.item.frame.minY))
}
return nil
}
}
func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil) -> NSAttributedString {
switch text {
case .empty:
@ -407,7 +484,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
let width: CGFloat
}
let extentBuffer = UnsafeMutablePointer<RunStruct>.allocate(capacity: 1)
extentBuffer.initialize(to: RunStruct(ascent: dimensions.height, 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
}, getAscent: { (pointer) -> CGFloat in
let d = pointer.assumingMemoryBound(to: RunStruct.self)
@ -420,7 +497,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
return d.pointee.width
})
let delegate = CTRunDelegateCreate(&callbacks, extentBuffer)
let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedStringKey): (delegate as Any), NSAttributedStringKey(rawValue: InstantPageMediaIdAttribute): id.id]
let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedStringKey): (delegate as Any), NSAttributedStringKey(rawValue: InstantPageMediaIdAttribute): id.id, NSAttributedStringKey(rawValue: InstantPageMediaDimensionsAttribute): dimensions]
return NSAttributedString(string: " ", attributes: attrDictionaryDelegate)
case let .anchor(text, name):
styleStack.push(.anchor(name))
@ -434,7 +511,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
}
}
func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0) -> (InstantPageTextItem?, [InstantPageItem], CGSize) {
func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0) -> (InstantPageTextItem?, [InstantPageItem], CGSize) {
if string.length == 0 {
return (nil, [], CGSize())
}
@ -470,12 +547,15 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
var lastIndex: CFIndex = 0
var currentLineOrigin = CGPoint()
var maxLineWidth: CGFloat = 0.0
var maxImageHeight: CGFloat = 0.0
var extraDescent: CGFloat = 0.0
let text = string.string
var indexOffset: CFIndex?
while true {
let currentMaxWidth = boundingWidth - currentLineOrigin.x
var workingLineOrigin = currentLineOrigin
let currentMaxWidth = boundingWidth - workingLineOrigin.x
let lineCharacterCount: CFIndex
var hadIndexOffset = false
if minimizeWidth {
@ -516,33 +596,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
stop = true
}
var strikethroughItems: [InstantPageTextStrikethroughItem] = []
var markedItems: [InstantPageTextMarkedItem] = []
var anchorItems: [InstantPageTextAnchorItem] = []
string.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedStringKey.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil))
strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: currentLineOrigin.x + lowerX, y: currentLineOrigin.y, width: upperX - lowerX, height: fontLineHeight)))
}
if let color = attributes[NSAttributedStringKey.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor {
var lineHeight = fontLineHeight
var delta: CGFloat = 0.0
if let offset = attributes[NSAttributedStringKey.baselineOffset] as? CGFloat {
lineHeight = floorToScreenPixels(lineHeight * 0.85)
delta = offset * 0.6
}
let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil))
markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: currentLineOrigin.x + lowerX, y: currentLineOrigin.y + delta, width: upperX - lowerX, height: lineHeight), color: color))
}
if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageAnchorAttribute)] as? String {
anchorItems.append(InstantPageTextAnchorItem(name: item))
}
}
let hadExtraDescent = extraDescent > 0.0
extraDescent = 0.0
var lineImageItems: [InstantPageTextImageItem] = []
var isRTL = false
@ -556,25 +610,22 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
let cfRunRange = CTRunGetStringRange(run)
let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length)
string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in
if let id = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaIdAttribute)] as? Int64 {
var imageFrame = CGRect()
var ascent: CGFloat = 0
imageFrame.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, nil, nil))
imageFrame.size.height = ascent
if let id = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaIdAttribute)] as? Int64, let dimensions = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaDimensionsAttribute)] as? CGSize {
var imageFrame = CGRect(origin: CGPoint(), size: dimensions)
let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels((fontLineHeight - imageFrame.size.height) / 2.0)
imageFrame.origin = imageFrame.origin.offsetBy(dx: currentLineOrigin.x + xOffset, dy: currentLineOrigin.y + yOffset)
imageFrame.origin = imageFrame.origin.offsetBy(dx: workingLineOrigin.x + xOffset, dy: workingLineOrigin.y + yOffset)
let minSpacing = fontLineSpacing - 3.0
let delta = currentLineOrigin.y - minSpacing - imageFrame.minY - appliedLineOffset
let minSpacing = fontLineSpacing - 4.0
let delta = workingLineOrigin.y - minSpacing - imageFrame.minY - appliedLineOffset
if !fontAscent.isZero && delta > 0.0 {
currentLineOrigin.y += delta
workingLineOrigin.y += delta
appliedLineOffset += delta
imageFrame.origin = imageFrame.origin.offsetBy(dx: 0.0, dy: delta)
}
if !fontLineHeight.isZero {
extraDescent = max(extraDescent, imageFrame.maxY - (currentLineOrigin.y + fontLineHeight + minSpacing))
extraDescent = max(extraDescent, imageFrame.maxY - (workingLineOrigin.y + fontLineHeight + minSpacing))
}
maxImageHeight = max(maxImageHeight, imageFrame.height)
lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: MediaId(namespace: Namespaces.Media.CloudFile, id: id)))
@ -583,19 +634,55 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
}
}
if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth, let imageItem = lineImageItems.last {
if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth + 1.0, let imageItem = lineImageItems.last {
indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound)
continue
}
var strikethroughItems: [InstantPageTextStrikethroughItem] = []
var markedItems: [InstantPageTextMarkedItem] = []
var anchorItems: [InstantPageTextAnchorItem] = []
string.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedStringKey.strikethroughStyle] {
let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil))
strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: workingLineOrigin.x + lowerX, y: workingLineOrigin.y, width: upperX - lowerX, height: fontLineHeight)))
}
if let color = attributes[NSAttributedStringKey.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor {
var lineHeight = fontLineHeight
var delta: CGFloat = 0.0
if let offset = attributes[NSAttributedStringKey.baselineOffset] as? CGFloat {
lineHeight = floorToScreenPixels(lineHeight * 0.85)
delta = offset * 0.6
}
let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil))
markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + lowerX, y: workingLineOrigin.y + delta, width: upperX - lowerX, height: lineHeight), color: color))
}
if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageAnchorAttribute)] as? String {
anchorItems.append(InstantPageTextAnchorItem(name: item))
}
}
if hadExtraDescent && extraDescent > 0 {
workingLineOrigin.y += fontLineSpacing
}
let height = !fontLineHeight.isZero ? fontLineHeight : maxImageHeight
let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: currentLineOrigin.x, y: currentLineOrigin.y, width: lineWidth, height: height), strikethroughItems: strikethroughItems, markedItems: markedItems, imageItems: lineImageItems, anchorItems: anchorItems, isRTL: isRTL)
let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: workingLineOrigin.x, y: workingLineOrigin.y, width: lineWidth, height: height), strikethroughItems: strikethroughItems, markedItems: markedItems, imageItems: lineImageItems, anchorItems: anchorItems, isRTL: isRTL)
lines.append(textLine)
imageItems.append(contentsOf: lineImageItems)
currentLineOrigin.x = 0.0;
currentLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent
if lineWidth > maxLineWidth {
maxLineWidth = lineWidth
}
workingLineOrigin.x = 0.0
workingLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent
currentLineOrigin = workingLineOrigin
lastIndex += lineCharacterCount
@ -612,23 +699,56 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
height = lines.last!.frame.maxY + extraDescent
}
let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: height), attributedString: string, alignment: alignment, lines: lines)
textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y)
var textWidth = boundingWidth
var requiresScroll = false
if maxLineWidth > boundingWidth + 10.0 {
textWidth = maxLineWidth
requiresScroll = true
}
let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: textWidth, height: height), attributedString: string, alignment: alignment, lines: lines)
if !requiresScroll {
textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y)
}
var items: [InstantPageItem] = []
if imageItems.isEmpty || string.length > 1 {
if !requiresScroll && (imageItems.isEmpty || string.length > 1) {
items.append(textItem)
}
var topInset: CGFloat = 0.0
var bottomInset: CGFloat = 0.0
var additionalItems: [InstantPageItem] = []
if let webpage = webpage {
let offset = requiresScroll ? CGPoint() : offset
for line in textItem.lines {
let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment)
for imageItem in line.imageItems {
if let image = media[imageItem.id] as? TelegramMediaFile {
items.append(InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false))
let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false)
additionalItems.append(item)
if item.frame.minY < topInset {
topInset = item.frame.minY
}
if item.frame.maxY > height {
bottomInset = max(bottomInset, item.frame.maxY - height)
}
}
}
}
}
return (textItem, items, textItem.frame.size)
if requiresScroll {
textItem.frame = textItem.frame.offsetBy(dx: 0.0, dy: fabs(topInset))
for var item in additionalItems {
item.frame = item.frame.offsetBy(dx: 0.0, dy: fabs(topInset))
}
let scrollableItem = InstantPageScrollableTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth + horizontalInset * 2.0, height: height + fabs(topInset) + bottomInset), item: textItem, additionalItems: additionalItems, totalWidth: textWidth, horizontalInset: horizontalInset, rtl: textItem.containsRTL)
items.append(scrollableItem)
} else {
items.append(contentsOf: additionalItems)
}
return (requiresScroll ? nil : textItem, items, textItem.frame.size)
}

View File

@ -25,6 +25,7 @@ enum InstantPageTextStyle {
let InstantPageLineSpacingFactorAttribute = "LineSpacingFactorAttribute"
let InstantPageMarkerColorAttribute = "MarkerColorAttribute"
let InstantPageMediaIdAttribute = "MediaIdAttribute"
let InstantPageMediaDimensionsAttribute = "MediaDimensionsAttribute"
let InstantPageAnchorAttribute = "AnchorAttribute"
final class InstantPageTextStyleStack {

View File

@ -103,9 +103,9 @@ final class InstantPageTheme {
let controlColor: UIColor
let imageEmptyColor: UIColor?
let imageTintColor: UIColor?
init(pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageEmptyColor: UIColor?) {
init(pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageTintColor: UIColor?) {
self.pageBackgroundColor = pageBackgroundColor
self.textCategories = textCategories
self.serif = serif
@ -122,11 +122,11 @@ final class InstantPageTheme {
self.tableBorderColor = tableBorderColor
self.tableHeaderColor = tableHeaderColor
self.controlColor = controlColor
self.imageEmptyColor = imageEmptyColor
self.imageTintColor = imageTintColor
}
func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTheme {
return InstantPageTheme(pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, linkColor: linkColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor, imageEmptyColor: imageEmptyColor)
return InstantPageTheme(pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, linkColor: linkColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor, imageTintColor: imageTintColor)
}
}
@ -156,7 +156,7 @@ private let lightTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0xe2e2e2),
tableHeaderColor: UIColor(rgb: 0xf4f4f4),
controlColor: UIColor(rgb: 0xc7c7cd),
imageEmptyColor: nil
imageTintColor: nil
)
private let sepiaTheme = InstantPageTheme(
@ -185,7 +185,7 @@ private let sepiaTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0xddd1b8),
tableHeaderColor: UIColor(rgb: 0xf0e7d4),
controlColor: UIColor(rgb: 0xddd1b8),
imageEmptyColor: nil
imageTintColor: nil
)
private let grayTheme = InstantPageTheme(
@ -214,7 +214,7 @@ private let grayTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0x484848),
tableHeaderColor: UIColor(rgb: 0x555556),
controlColor: UIColor(rgb: 0x484848),
imageEmptyColor: nil
imageTintColor: UIColor(rgb: 0xcecece)
)
private let darkTheme = InstantPageTheme(
@ -243,7 +243,7 @@ private let darkTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0x303030),
tableHeaderColor: UIColor(rgb: 0x131313),
controlColor: UIColor(rgb: 0x303030),
imageEmptyColor: UIColor(rgb: 0xb0b0b0)
imageTintColor: UIColor(rgb: 0xb0b0b0)
)
private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat {

View File

@ -91,6 +91,16 @@ public func parseConfirmationCodeUrl(_ url: URL) -> Int? {
return code
}
}
if url.scheme == "tg" {
if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(query: host + "?" + query) {
switch parsedUrl {
case let .confirmationCode(code):
return code
default:
break
}
}
}
return nil
}

View File

@ -2057,9 +2057,9 @@ func instantPageImageFile(account: Account, fileReference: FileMediaReference, f
context.withFlippedContext { c in
if var fullSizeImage = fullSizeImage {
// if true || imageIsMonochrome(fullSizeImage), let tintedImage = generateTintedImage(image: UIImage(cgImage: fullSizeImage), color: .white)?.cgImage {
// fullSizeImage = tintedImage
// }
if let color = arguments.emptyColor, imageRequiresInversion(fullSizeImage), let tintedImage = generateTintedImage(image: UIImage(cgImage: fullSizeImage), color: color)?.cgImage {
fullSizeImage = tintedImage
}
c.setBlendMode(.normal)
c.interpolationQuality = .medium

View File

@ -228,7 +228,7 @@ public final class PresentationCallManager {
private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) {
if let firstState = ringingStates.first {
if self.currentCall == nil {
let call = PresentationCall(account: self.account, audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? self.callKitIntegration : nil, serializedData: self.callSettings?.1.serializedData, dataSaving: self.callSettings?.0.dataSaving ?? .never, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType)
let call = PresentationCall(account: self.account, audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings?.0) : nil, serializedData: self.callSettings?.1.serializedData, dataSaving: self.callSettings?.0.dataSaving ?? .never, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType)
self.currentCall = call
self.currentCallPromise.set(.single(call))
self.hasActiveCallsPromise.set(true)

File diff suppressed because it is too large Load Diff

View File

@ -257,7 +257,7 @@ public final class ShareController: ViewController {
showInChat(message)
})
}
if let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, messages.count == 1 || sameGroupingKey {
else if let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, messages.count == 1 || sameGroupingKey {
if message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty {
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.ShareMenu_CopyShareLink, action: { [weak self] in
UIPasteboard.general.string = "https://t.me/\(addressName)/\(message.id.id)"

View File

@ -172,7 +172,6 @@ final class StickerPaneSearchContainerNode: ASDisplayNode {
self.gridNode = GridNode()
self.notFoundNode = ASImageNode()
self.notFoundNode.isLayerBacked = true
self.notFoundNode.displayWithoutProcessing = true
self.notFoundNode.displaysAsynchronously = false
self.notFoundNode.clipsToBounds = false

View File

@ -320,18 +320,22 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var mediaFileStatus: Signal<MediaResourceStatus?, NoError> = .single(nil)
if let contentInfo = item.contentInfo, case let .message(message) = contentInfo {
var file: TelegramMediaFile?
var isWebpage = false
for m in message.media {
if let m = m as? TelegramMediaFile, m.isVideo {
file = m
break
} else if let m = m as? TelegramMediaWebpage, case let .Loaded(content) = m.content, let f = content.file, f.isVideo {
file = f
isWebpage = true
break
}
}
if let file = file {
let status = messageMediaFileStatus(account: item.account, messageId: message.id, file: file)
self.scrubberView.setFetchStatusSignal(status, strings: self.strings, fileSize: file.size)
if !isWebpage {
self.scrubberView.setFetchStatusSignal(status, strings: self.strings, fileSize: file.size)
}
self.requiresDownload = !isMediaStreamable(message: message, media: file)
mediaFileStatus = status |> map(Optional.init)

View File

@ -4,13 +4,13 @@ import Postbox
import TelegramCore
import MtProtoKitDynamic
private enum ParsedInternalPeerUrlParameter {
enum ParsedInternalPeerUrlParameter {
case botStart(String)
case groupBotStart(String)
case channelMessage(Int32)
}
private enum ParsedInternalUrl {
enum ParsedInternalUrl {
case peerName(String, ParsedInternalPeerUrlParameter?)
case stickerPack(String)
case join(String)
@ -39,7 +39,7 @@ enum ResolvedUrl {
case confirmationCode(Int)
}
private func parseInternalUrl(query: String) -> ParsedInternalUrl? {
func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if let components = URLComponents(string: "/" + query) {
var pathComponents = components.path.components(separatedBy: "/")
if !pathComponents.isEmpty {
@ -91,6 +91,18 @@ private func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if let _ = url {
return .internalInstantView(url: "https://t.me/\(query)")
}
} else if peerName == "login" {
var code: String?
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "code" {
code = value
}
}
}
if let code = code, let codeValue = Int(code) {
return .confirmationCode(codeValue)
}
} else {
for queryItem in queryItems {
if let value = queryItem.value {