mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-01 12:17:53 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/TelegramUI
# Conflicts: # TelegramUI/PresentationStrings.swift # TelegramUI/Resources/PresentationStrings.mapping
This commit is contained in:
commit
7a4c6f8bfc
@ -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 */,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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? {
|
||||
|
||||
106
TelegramUI/InstantPageScrollableNode.swift
Normal file
106
TelegramUI/InstantPageScrollableNode.swift
Normal 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) {
|
||||
}
|
||||
}
|
||||
@ -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]
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ enum InstantPageTextStyle {
|
||||
let InstantPageLineSpacingFactorAttribute = "LineSpacingFactorAttribute"
|
||||
let InstantPageMarkerColorAttribute = "MarkerColorAttribute"
|
||||
let InstantPageMediaIdAttribute = "MediaIdAttribute"
|
||||
let InstantPageMediaDimensionsAttribute = "MediaDimensionsAttribute"
|
||||
let InstantPageAnchorAttribute = "AnchorAttribute"
|
||||
|
||||
final class InstantPageTextStyleStack {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
Binary file not shown.
@ -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)"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user